rubysspi 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.txt ADDED
@@ -0,0 +1,13 @@
1
+ == 1.2.2
2
+ * Fixed incompatibility with RubyGems >= 0.9.2 and RubySSPI Net::HTTP patch. Patch now directly requires the SSPI files, rather than relying on RubyGems to load it.
3
+ * Updated gem tests for compatibility with RubyGems >= 0.9.2.
4
+
5
+ == 1.2.0
6
+ * Refactored naming in preparation for inclusion in ruby distribution
7
+ * rubysspi.rb renamed to win32/sspi.rb
8
+ * rubysspi/proxy_auth.rb renamed to win32/net/http_proxy_patch.rb
9
+ * Updated apply_sspi_patch to determine if RubySSPI gem is installed and fail if not. Also fails if the patch has already been installed.
10
+ * Added test to "test_patched_net_http.rb" to ensure net/http has been patched first.
11
+ * Removed Win32 platform restriction on gem (though it will still only run in Win32 environments, real or emulated).
12
+ * CHANGELOG begun
13
+
data/LICENSE.txt ADDED
@@ -0,0 +1,11 @@
1
+ #
2
+ # = RubySSPI
3
+ #
4
+ # Copyright (c) 2006-2007 Justin Bailey
5
+ #
6
+ # Written and maintained by Justin Bailey <jgbailey@gmail.com>.
7
+ #
8
+ # This program is free software. You can re-distribute and/or
9
+ # modify this program under the same terms of ruby itself ---
10
+ # Ruby Distribution License or GNU General Public License.
11
+ #
data/README.txt ADDED
@@ -0,0 +1,131 @@
1
+ = Introduction
2
+
3
+ This library provides bindings to the Win32 SSPI libraries, which implement various security protocols for Windows. The library was primarily developed to give Negotiate/NTLM proxy authentication abilities to Net::HTTP, similar to support found in Internet Explorer or Firefox.
4
+
5
+ The libary is NOT an implementation of the NTLM protocol, and does not give the ability to authenticate as any given user. It is able to authenticate with a proxy server as the current user.
6
+
7
+ This project can be found on rubyforge at:
8
+
9
+ http://rubyforge.org/projects/rubysspi
10
+
11
+ = Using with rubygems (and other libraries)
12
+
13
+ If RubyGems is not working because of proxy authorization errors, the rubysspi library can be used to solve the problem. The gem includes a file called <tt>spa.rb</tt> in the root directory. Copy this file to your site_ruby directory and then run the gem script directly:
14
+
15
+ ruby -rspa 'C:\Program Files\ruby\gem'
16
+
17
+ For example, to list remote gems that match "sspi" this command can be used
18
+
19
+ ruby -rspa 'C:\Program Files\ruby\gem' list --remote sspi
20
+
21
+ You must copy the <tt>spa.rb</tt> file yourself, however. Rubygems is not able to do it for you on install.
22
+
23
+ = Using with open-uri
24
+
25
+ To use the library with open-uri, make sure to set the environment variable +http_proxy+ to your proxy server. This must be a hostname and port in URL form. E.g.:
26
+
27
+ http://proxy.corp.com:8080
28
+
29
+ The library will grab your current username and domain from the environment variables +USERNAME+ and +USERDOMAIN+. This should be set for you by Windows already.
30
+
31
+ The library implements a patch on top of Net::HTTP, which means open-uri gets it too. At the top of your script, make sure to require the patch after <tt>open-uri</tt>:
32
+
33
+ require 'open-uri'
34
+ require 'win32/sspi/http_proxy_patch'
35
+
36
+ open("http://www.google.com") { |f| puts(f.gets(nil)) }
37
+
38
+ Note that this patch does NOT work with the +http_proxy_user+ and +http_proxy_password+ environment variables. The library will ONLY authenticate as the current user.
39
+
40
+ = Using with Net::HTTP
41
+
42
+ Net::HTTP will not use the proxy server supplied in the environment variable automatically, so you have to supply the proxy address yourself. Otherwise, it's exactly the same:
43
+
44
+ require 'net/http'
45
+ require 'win32/sspi/http_proxy_patch'
46
+
47
+ Net::HTTP::Proxy("proxy.corp.com", 8080).start("www.google.com") do |http|
48
+ resp = http.request_get "/"
49
+ puts resp.body
50
+ end
51
+
52
+ = Using rubysspi directly
53
+
54
+ As stated, the library is geared primarily towards supporting Negotiate/NTLM authentication with proxy servers. In this vein, you can manually authenticate a given HTTP connection with a single call:
55
+
56
+ require 'win32/sspi'
57
+
58
+ Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
59
+ resp = SSPI::NegotiateAuth.proxy_auth_get http, "/"
60
+ end
61
+
62
+ The +resp+ variable will contain the response from Google, with any proxy authorization necessary taken care of automatically. Note that if the +http+ connection is not closed, any subsequent requests will NOT require authentication.
63
+
64
+ If the above method is used, you should NOT require the 'win32/sspi/http_proxy_patch' library, as the interaction between the two will fail.
65
+
66
+ The library can be used directly to generate tokens appropriate for the current user, too.
67
+
68
+ To get started, first create an instance of the SSPI::NegotiateAuth class:
69
+
70
+ require 'win32/sspi'
71
+
72
+ n = SSPI::NegotiateAuth.new
73
+
74
+ Next, get the first token by calling get_initial_token:
75
+
76
+ token = n.get_initial_token
77
+
78
+ This token returned will be Base64 encoded and can be directly placed in an HTTP header. This token can be easily decoded, however, and is usually an NTLM Type 1 message.
79
+
80
+ After getting a response from the server (usually an NTLM Type 2 message), pass it into the complete_authentication:
81
+
82
+ token = n.complete_authentication(server_token)
83
+
84
+ Note that server_token can be Base64 encoded or not, and if it starts with "Negotiate", that phrase will be stripped off. This allows the response from a Proxy-Authentication header to be passed into the method directly. The token can be decoded externally and passed in, too.
85
+
86
+ The token returned (usually an NTLM Type 3) message can then be sent to the server and the connection should be authenticated.
87
+
88
+ = Patching Ruby
89
+
90
+ A short script to patch Net::HTTP is also included. This patch will give the Net::HTTP (and consequently, open-uri) the ability to directly authenticate with Negotiate/NTLM proxy servers. The main advantage of patching Ruby itself is that other scripts, such as gems, will work directly with these proxy servers and will not require running with the 'spa' library. Similarly, any scripts which use open-uri or Net::HTTP will gain the ability to authenticate with the proxy server.
91
+
92
+ To run the script, just run <tt>apply_sspi_patch</tt>. The backup will be made of the original 'http.rb' file, and a new patched file will be copied in. After patching, run the test script 'test\test_patched_net_http.rb'. It ensures that proxy authentication now works without extra libraries.
93
+
94
+ Be aware - this has only been tested on Ruby 1.8.4 and 1.8.5. Though the Net::HTTP libraries are not likely to change much, do some testing afterwards.
95
+
96
+ If you want the patch disabled for a certain script, just define the constant DISABLE_RUBY_SSPI_PATCH at the top level. If you want to back the patch out, go to the ruby library directory, open the net folder, and rename the file called "http.orig.X.rb" (where X is some number) to http.rb. If the patch has been applied multiple times, uses the lowest "X" found.
97
+
98
+ = Upgrading to a new version of the library
99
+
100
+ If you have installed the RubySSPI library previously, and wish to upgrade to a new version, follow these steps:
101
+
102
+ - Install the new version of the gem
103
+ - If you applied the patch before, go to your Ruby library directory (under the One-click installer, its usually ruby\lib\ruby\1.8):
104
+ - If any http.orig.N.rb files exist (where N is a number), then delete http.rb and rename the lowest http.orig.N.rb file to http.rb
105
+ - Delete any other nttp.orig.N.rb files.
106
+ - Run "apply_sspi_patch" to apply the patch again
107
+
108
+ = Disabling proxy for certain servers
109
+
110
+ Sometimes it is necessary to NOT use the proxy for certain addresses, especially in a LAN environment. While not a feature directly provided by this library, setting the 'no_proxy' environment variable to a list of servers will ensure no proxy connection is made for them. The list should be comma-delimited, and can consist of any mix of domain names and ports. URLs, however, are not allowed. For example "somehost, somehost.corporate.com, myhost:9000, myhost.corporate.com:80" would all be legal. Note that if a port is NOT specified, then no proxying occurs for that host regardless of the port requested by open-uri.
111
+
112
+ Finally, the above technique will only work with the open-uri library. Net::HTTP will not use a proxy automatically in any case.
113
+
114
+ = Thanks & References
115
+
116
+ Many references were used in decoding both NTLM messages and integrating with the SSPI library. Among them are:
117
+
118
+ * Managed SSPI Sample - A .NET implementation of a simple client/server using SSPI. A complex undertaking but provides a great resource for playing with the API.
119
+ * http://msdn.microsoft.com/library/?url=/library/en-us/dndotnet/html/remsspi.asp?frame=true
120
+ * John Lam's RubyCLR - http://www.rubyclr.com
121
+ * Originally, I used RubyCLR to call into the Managed SSPI sample which really helped me decode what the SSPI interface did and how it worked. I did not end up using that implementation but it was great for research.
122
+ * The NTLM Authentication Protocol - The definitive explanation for the NTLM protocol (outside MS internal documents, I presume).
123
+ * http://davenport.sourceforge.net/ntlm.html
124
+ * Ruby/NTLM - A pure Ruby implementation of the NTLM protocol. Again, not used in this project but invaluable for decoding NTLM messages and figuring out what SSPI was returning.
125
+ * http://rubyforge.org/projects/rubyntlm/
126
+ * Seamonkey/Mozilla NTLM implementation - The only source for an implementation in an actual browser. How they figured out how to use SSPI themselves is beyond me.
127
+ * http://lxr.mozilla.org/seamonkey/source/mailnews/base/util/nsMsgProtocol.cpp#899
128
+
129
+ And of course, thanks to my Lord and Savior, Jesus Christ. In the name of the Father, the Son, and the Holy Spirit.
130
+
131
+
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ require 'rubygems'
2
+ Gem::manage_gems
3
+ require 'rake/gempackagetask'
4
+ require 'rake/testtask'
5
+
6
+ spec = Gem::Specification.new do |s|
7
+ s.name = "rubysspi"
8
+ s.summary = "A library which implements Ruby bindings to the Win32 SSPI library. Also includes a module to add Negotiate authentication support to Net::HTTP."
9
+ s.version = "1.2.2"
10
+ s.author = "Justin Bailey"
11
+ s.email = "jgbailey @nospam@ gmail.com"
12
+ s.homepage = "http://rubyforge.org/projects/rubysspi/"
13
+ s.rubyforge_project = "http://rubyforge.org/projects/rubysspi/"
14
+ s.description = <<EOS
15
+ This gem provides bindings to the Win32 SSPI libraries, primarily to support Negotiate (i.e. SPNEGO, NTLM)
16
+ authentication with a proxy server. Enough support is implemented to provide the necessary support for
17
+ the authentication.
18
+
19
+ A module is also provided which overrides Net::HTTP and adds support for Negotiate authentication to it.
20
+
21
+ This implies that open-uri automatically gets support for it, as long as the http_proxy environment variable
22
+ is set.
23
+ EOS
24
+
25
+ s.files = FileList["lib/**/*", "test/*", "*.txt", "Rakefile", "spa.rb"].to_a
26
+ s.bindir = "bin"
27
+ s.executables = ["apply_sspi_patch"]
28
+
29
+ s.require_path = "lib"
30
+
31
+ s.has_rdoc = true
32
+ s.extra_rdoc_files = ["README.txt", "CHANGELOG.txt", "LICENSE.txt"]
33
+ s.rdoc_options << '--title' << 'Ruby SSPI -- Win32 SSPI Bindings for Ruby' <<
34
+ '--main' << 'README.txt' <<
35
+ '--line-numbers'
36
+ end
37
+
38
+ Rake::GemPackageTask.new(spec) do |pkg|
39
+ pkg.need_tar = true
40
+ end
@@ -0,0 +1,83 @@
1
+ #
2
+ # = bin/apply_sspi_patch
3
+ #
4
+ # Copyright (c) 2006-2007 Justin Bailey
5
+ #
6
+ # Written and maintained by Justin Bailey <jgbailey@gmail.com>.
7
+ #
8
+ # This program is free software. You can re-distribute and/or
9
+ # modify this program under the same terms of ruby itself ---
10
+ # Ruby Distribution License or GNU General Public License.
11
+ #
12
+
13
+ # This file will patch the local net/http installation to include support for NTLM/Negotiate authentication.
14
+ # The original http.rb file will be renamed to http.orig.N.rb
15
+
16
+ require 'rbconfig'
17
+ require 'pathname'
18
+ require 'fileutils'
19
+
20
+ begin
21
+ require 'net/http'
22
+ if Net::HTTP.sspi?
23
+ puts <<-EOF
24
+ You don't need to patch - it appears to have already been installed.
25
+
26
+ If you wish to upgrade your patch, please delete the previous patch by
27
+ renaming net/http.orig.1.rb to http.rb, and deleting any other
28
+ http.orig.N.rb files which may exist, then run this file again.
29
+ EOF
30
+
31
+ exit
32
+ end
33
+
34
+ rescue NoMethodError
35
+ end
36
+
37
+ begin
38
+ require 'rubygems'
39
+ require 'win32/sspi/http_proxy_patch'
40
+ rescue LoadError
41
+ puts "Unable to load ruby gems or the rubysspi gem. You cannot patch until the gem is installed."
42
+ exit
43
+ end
44
+
45
+ # Original http.rb file
46
+ orig_file = Pathname.new(Config::CONFIG["rubylibdir"]) + 'net\http.rb'
47
+
48
+ # Determine that original actually exists
49
+ raise "Original http.rb not not found at expected location #{orig_file}" unless orig_file.exist?
50
+
51
+ # Determine name to copy original file to
52
+ @i += 1 while (orig_dest = Pathname.new(Config::CONFIG["rubylibdir"]) + "net\\http.orig.#{@i ||= 1}.rb").exist?
53
+
54
+ # Copy original to back up
55
+ puts "Backing up original #{orig_file} to #{orig_dest}"
56
+ FileUtils.cp orig_file, orig_dest
57
+
58
+ # Get path to current version of the gem.
59
+ spec = Gem::GemPathSearcher.new.find("win32/sspi")
60
+ unless spec
61
+ puts "Unable to find path win32/sspi in gem repository. Can't install patch."
62
+ exit
63
+ end
64
+
65
+ gem_path = Pathname.new(spec.full_gem_path) + spec.require_path
66
+
67
+ # Write patch file.
68
+ puts "Creating patch file to replace #{orig_file}"
69
+ orig_file.open("w") { |f| f.write(<<EOS) }
70
+ # Include original net/http
71
+ require 'net/#{orig_dest.basename(orig_dest.extname)}'
72
+
73
+ # This magic constant can be defined to disable the patch.
74
+ unless defined? DISABLE_RUBY_SSPI_PATCH
75
+ # Because rubygems >= 0.9.2 requires net/http,
76
+ # we can't depend on ruby gems here. Instead, directly
77
+ # require path containing RubySSPI gem.
78
+ $: << "#{gem_path}" unless $:.include?("#{gem_path}")
79
+ require 'win32/sspi/http_proxy_patch'
80
+ end
81
+ EOS
82
+
83
+ puts 'Patch applied! Please run ..\test\test_patched_net_http.rb to test the patch.'
data/lib/win32/sspi.rb ADDED
@@ -0,0 +1,331 @@
1
+ #
2
+ # = win32/sspi.rb
3
+ #
4
+ # Copyright (c) 2006-2007 Justin Bailey
5
+ #
6
+ # Written and maintained by Justin Bailey <jgbailey@gmail.com>.
7
+ #
8
+ # This program is free software. You can re-distribute and/or
9
+ # modify this program under the same terms of ruby itself ---
10
+ # Ruby Distribution License or GNU General Public License.
11
+ #
12
+
13
+ require 'Win32API'
14
+ require 'base64'
15
+
16
+ # Implements bindings to Win32 SSPI functions, focused on authentication to a proxy server over HTTP.
17
+ module Win32
18
+ module SSPI
19
+ # Specifies how credential structure requested will be used. Only SECPKG_CRED_OUTBOUND is used
20
+ # here.
21
+ SECPKG_CRED_INBOUND = 0x00000001
22
+ SECPKG_CRED_OUTBOUND = 0x00000002
23
+ SECPKG_CRED_BOTH = 0x00000003
24
+
25
+ # Format of token. NETWORK format is used here.
26
+ SECURITY_NATIVE_DREP = 0x00000010
27
+ SECURITY_NETWORK_DREP = 0x00000000
28
+
29
+ # InitializeSecurityContext Requirement flags
30
+ ISC_REQ_REPLAY_DETECT = 0x00000004
31
+ ISC_REQ_SEQUENCE_DETECT = 0x00000008
32
+ ISC_REQ_CONFIDENTIALITY = 0x00000010
33
+ ISC_REQ_USE_SESSION_KEY = 0x00000020
34
+ ISC_REQ_PROMPT_FOR_CREDS = 0x00000040
35
+ ISC_REQ_CONNECTION = 0x00000800
36
+
37
+ # Win32 API Functions. Uses Win32API to bind methods to constants contained in class.
38
+ module API
39
+ # Can be called with AcquireCredentialsHandle.call()
40
+ AcquireCredentialsHandle = Win32API.new("secur32", "AcquireCredentialsHandle", 'ppLpppppp', 'L')
41
+ # Can be called with InitializeSecurityContext.call()
42
+ InitializeSecurityContext = Win32API.new("secur32", "InitializeSecurityContext", 'pppLLLpLpppp', 'L')
43
+ # Can be called with DeleteSecurityContext.call()
44
+ DeleteSecurityContext = Win32API.new("secur32", "DeleteSecurityContext", 'P', 'L')
45
+ # Can be called with FreeCredentialsHandle.call()
46
+ FreeCredentialsHandle = Win32API.new("secur32", "FreeCredentialsHandle", 'P', 'L')
47
+ end
48
+
49
+ # SecHandle struct
50
+ class SecurityHandle
51
+ def upper
52
+ @struct.unpack("LL")[1]
53
+ end
54
+
55
+ def lower
56
+ @struct.unpack("LL")[0]
57
+ end
58
+
59
+ def to_p
60
+ @struct ||= "\0" * 8
61
+ end
62
+ end
63
+
64
+ # Some familiar aliases for the SecHandle structure
65
+ CredHandle = CtxtHandle = SecurityHandle
66
+
67
+ # TimeStamp struct
68
+ class TimeStamp
69
+ attr_reader :struct
70
+
71
+ def to_p
72
+ @struct ||= "\0" * 8
73
+ end
74
+ end
75
+
76
+ # Creates binary representaiton of a SecBufferDesc structure,
77
+ # including the SecBuffer contained inside.
78
+ class SecurityBuffer
79
+
80
+ SECBUFFER_TOKEN = 2 # Security token
81
+
82
+ TOKENBUFSIZE = 12288
83
+ SECBUFFER_VERSION = 0
84
+
85
+ def initialize(buffer = nil)
86
+ @buffer = buffer || "\0" * TOKENBUFSIZE
87
+ @bufferSize = @buffer.length
88
+ @type = SECBUFFER_TOKEN
89
+ end
90
+
91
+ def bufferSize
92
+ unpack
93
+ @bufferSize
94
+ end
95
+
96
+ def bufferType
97
+ unpack
98
+ @type
99
+ end
100
+
101
+ def token
102
+ unpack
103
+ @buffer
104
+ end
105
+
106
+ def to_p
107
+ # Assumption is that when to_p is called we are going to get a packed structure. Therefore,
108
+ # set @unpacked back to nil so we know to unpack when accessors are next accessed.
109
+ @unpacked = nil
110
+ # Assignment of inner structure to variable is very important here. Without it,
111
+ # will not be able to unpack changes to the structure. Alternative, nested unpacks,
112
+ # does not work (i.e. @struct.unpack("LLP12")[2].unpack("LLP12") results in "no associated pointer")
113
+ @sec_buffer ||= [@bufferSize, @type, @buffer].pack("LLP")
114
+ @struct ||= [SECBUFFER_VERSION, 1, @sec_buffer].pack("LLP")
115
+ end
116
+
117
+ private
118
+
119
+ # Unpacks the SecurityBufferDesc structure into member variables. We
120
+ # only want to do this once per struct, so the struct is deleted
121
+ # after unpacking.
122
+ def unpack
123
+ if ! @unpacked && @sec_buffer && @struct
124
+ @bufferSize, @type = @sec_buffer.unpack("LL")
125
+ @buffer = @sec_buffer.unpack("LLP#{@bufferSize}")[2]
126
+ @struct = nil
127
+ @sec_buffer = nil
128
+ @unpacked = true
129
+ end
130
+ end
131
+ end
132
+
133
+ # SEC_WINNT_AUTH_IDENTITY structure
134
+ class Identity
135
+ SEC_WINNT_AUTH_IDENTITY_ANSI = 0x1
136
+
137
+ attr_accessor :user, :domain, :password
138
+
139
+ def initialize(user = nil, domain = nil, password = nil)
140
+ @user = user
141
+ @domain = domain
142
+ @password = password
143
+ @flags = SEC_WINNT_AUTH_IDENTITY_ANSI
144
+ end
145
+
146
+ def to_p
147
+ [@user, @user ? @user.length : 0,
148
+ @domain, @domain ? @domain.length : 0,
149
+ @password, @password ? @password.length : 0,
150
+ @flags].pack("PLPLPLL")
151
+ end
152
+ end
153
+
154
+ # Takes a return result from an SSPI function and interprets the value.
155
+ class SSPIResult
156
+ # Good results
157
+ SEC_E_OK = 0x00000000
158
+ SEC_I_CONTINUE_NEEDED = 0x00090312
159
+
160
+ # These are generally returned by InitializeSecurityContext
161
+ SEC_E_INSUFFICIENT_MEMORY = 0x80090300
162
+ SEC_E_INTERNAL_ERROR = 0x80090304
163
+ SEC_E_INVALID_HANDLE = 0x80090301
164
+ SEC_E_INVALID_TOKEN = 0x80090308
165
+ SEC_E_LOGON_DENIED = 0x8009030C
166
+ SEC_E_NO_AUTHENTICATING_AUTHORITY = 0x80090311
167
+ SEC_E_NO_CREDENTIALS = 0x8009030E
168
+ SEC_E_TARGET_UNKNOWN = 0x80090303
169
+ SEC_E_UNSUPPORTED_FUNCTION = 0x80090302
170
+ SEC_E_WRONG_PRINCIPAL = 0x80090322
171
+
172
+ # These are generally returned by AcquireCredentialsHandle
173
+ SEC_E_NOT_OWNER = 0x80090306
174
+ SEC_E_SECPKG_NOT_FOUND = 0x80090305
175
+ SEC_E_UNKNOWN_CREDENTIALS = 0x8009030D
176
+
177
+ @@map = {}
178
+ constants.each { |v| @@map[self.const_get(v.to_s)] = v }
179
+
180
+ attr_reader :value
181
+
182
+ def initialize(value)
183
+ # convert to unsigned long
184
+ value = [value].pack("L").unpack("L").first
185
+ raise "#{value.to_s(16)} is not a recognized result" unless @@map.has_key? value
186
+ @value = value
187
+ end
188
+
189
+ def to_s
190
+ @@map[@value].to_s
191
+ end
192
+
193
+ def ok?
194
+ @value == SEC_I_CONTINUE_NEEDED || @value == SEC_E_OK
195
+ end
196
+
197
+ def ==(other)
198
+ if other.is_a?(SSPIResult)
199
+ @value == other.value
200
+ elsif other.is_a?(Fixnum)
201
+ @value == @@map[other]
202
+ else
203
+ false
204
+ end
205
+ end
206
+ end
207
+
208
+ # Handles "Negotiate" type authentication. Geared towards authenticating with a proxy server over HTTP
209
+ class NegotiateAuth
210
+ attr_accessor :credentials, :context, :contextAttributes, :user, :domain
211
+
212
+ # Default request flags for SSPI functions
213
+ REQUEST_FLAGS = ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONNECTION
214
+
215
+ # NTLM tokens start with this header always. Encoding alone adds "==" and newline, so remove those
216
+ B64_TOKEN_PREFIX = Base64.encode64("NTLMSSP").delete("=\n")
217
+
218
+ # Given a connection and a request path, performs authentication as the current user and returns
219
+ # the response from a GET request. The connnection should be a Net::HTTP object, and it should
220
+ # have been constructed using the Net::HTTP.Proxy method, but anything that responds to "get" will work.
221
+ # If a user and domain are given, will authenticate as the given user.
222
+ # Returns the response received from the get method (usually Net::HTTPResponse)
223
+ def NegotiateAuth.proxy_auth_get(http, path, user = nil, domain = nil)
224
+ raise "http must respond to :get" unless http.respond_to?(:get)
225
+ nego_auth = self.new user, domain
226
+
227
+ resp = http.get path, { "Proxy-Authorization" => "Negotiate " + nego_auth.get_initial_token }
228
+ if resp["Proxy-Authenticate"]
229
+ resp = http.get path, { "Proxy-Authorization" => "Negotiate " + nego_auth.complete_authentication(resp["Proxy-Authenticate"].split(" ").last.strip) }
230
+ end
231
+
232
+ resp
233
+ end
234
+
235
+ # Creates a new instance ready for authentication as the given user in the given domain.
236
+ # Defaults to current user and domain as defined by ENV["USERDOMAIN"] and ENV["USERNAME"] if
237
+ # no arguments are supplied.
238
+ def initialize(user = nil, domain = nil)
239
+ if user.nil? && domain.nil? && ENV["USERNAME"].nil? && ENV["USERDOMAIN"].nil?
240
+ raise "A username or domain must be supplied since they cannot be retrieved from the environment"
241
+ end
242
+
243
+ @user = user || ENV["USERNAME"]
244
+ @domain = domain || ENV["USERDOMAIN"]
245
+ end
246
+
247
+ # Gets the initial Negotiate token. Returns it as a base64 encoded string suitable for use in HTTP. Can
248
+ # be easily decoded, however.
249
+ def get_initial_token
250
+ raise "This object is no longer usable because its resources have been freed." if @cleaned_up
251
+ get_credentials
252
+
253
+ outputBuffer = SecurityBuffer.new
254
+ @context = CtxtHandle.new
255
+ @contextAttributes = "\0" * 4
256
+
257
+ result = SSPIResult.new(API::InitializeSecurityContext.call(@credentials.to_p, nil, nil,
258
+ REQUEST_FLAGS,0, SECURITY_NETWORK_DREP, nil, 0, @context.to_p, outputBuffer.to_p, @contextAttributes, TimeStamp.new.to_p))
259
+
260
+ if result.ok? then
261
+ return encode_token(outputBuffer.token)
262
+ else
263
+ raise "Error: #{result.to_s}"
264
+ end
265
+ end
266
+
267
+ # Takes a token and gets the next token in the Negotiate authentication chain. Token can be Base64 encoded or not.
268
+ # The token can include the "Negotiate" header and it will be stripped.
269
+ # Does not indicate if SEC_I_CONTINUE or SEC_E_OK was returned.
270
+ # Token returned is Base64 encoded w/ all new lines removed.
271
+ def complete_authentication(token)
272
+ raise "This object is no longer usable because its resources have been freed." if @cleaned_up
273
+
274
+ # Nil token OK, just set it to empty string
275
+ token = "" if token.nil?
276
+
277
+ if token.include? "Negotiate"
278
+ # If the Negotiate prefix is passed in, assume we are seeing "Negotiate <token>" and get the token.
279
+ token = token.split(" ").last
280
+ end
281
+
282
+ if token.include? B64_TOKEN_PREFIX
283
+ # indicates base64 encoded token
284
+ token = Base64.decode64(token.strip)
285
+ end
286
+
287
+ outputBuffer = SecurityBuffer.new
288
+ result = SSPIResult.new(API::InitializeSecurityContext.call(@credentials.to_p, @context.to_p, nil,
289
+ REQUEST_FLAGS, 0, SECURITY_NETWORK_DREP, SecurityBuffer.new(token).to_p, 0,
290
+ @context.to_p,
291
+ outputBuffer.to_p, @contextAttributes, TimeStamp.new.to_p))
292
+
293
+ if result.ok? then
294
+ return encode_token(outputBuffer.token)
295
+ else
296
+ raise "Error: #{result.to_s}"
297
+ end
298
+ ensure
299
+ # need to make sure we don't clean up if we've already cleaned up.
300
+ clean_up unless @cleaned_up
301
+ end
302
+
303
+ private
304
+
305
+ def clean_up
306
+ # free structures allocated
307
+ @cleaned_up = true
308
+ API::FreeCredentialsHandle.call(@credentials.to_p)
309
+ API::DeleteSecurityContext.call(@context.to_p)
310
+ @context = nil
311
+ @credentials = nil
312
+ @contextAttributes = nil
313
+ end
314
+
315
+ # Gets credentials based on user, domain or both. If both are nil, an error occurs
316
+ def get_credentials
317
+ @credentials = CredHandle.new
318
+ ts = TimeStamp.new
319
+ @identity = Identity.new @user, @domain
320
+ result = SSPIResult.new(API::AcquireCredentialsHandle.call(nil, "Negotiate", SECPKG_CRED_OUTBOUND, nil, @identity.to_p,
321
+ nil, nil, @credentials.to_p, ts.to_p))
322
+ raise "Error acquire credentials: #{result}" unless result.ok?
323
+ end
324
+
325
+ def encode_token(t)
326
+ # encode64 will add newlines every 60 characters so we need to remove those.
327
+ Base64.encode64(t).delete("\n")
328
+ end
329
+ end
330
+ end
331
+ end
@@ -0,0 +1,88 @@
1
+ #
2
+ # = win32/net/http_proxy_patch.rb
3
+ #
4
+ # Copyright (c) 2006-2007 Justin Bailey
5
+ #
6
+ # Written and maintained by Justin Bailey <jgbailey@gmail.com>.
7
+ #
8
+ # This file extends code originally found in lib/net/net.http,
9
+ # see that file for attribution.
10
+ #
11
+ # This program is free software. You can re-distribute and/or
12
+ # modify this program under the same terms of ruby itself ---
13
+ # Ruby Distribution License or GNU General Public License.
14
+ #
15
+
16
+ require 'net/http'
17
+ require 'win32/sspi'
18
+
19
+ module Net
20
+ # Replaces Net::HTTP.request to understand Negotiate HTTP proxy authorization. Uses native Win32
21
+ # libraries to authentic as the current user (as defined by the ENV["USERNAME"] and ENV["USERDOMAIN"]
22
+ # environment variables.
23
+ class HTTP
24
+
25
+ # Indicates that net/http has been patched by SSPI.
26
+ def self.sspi?
27
+ true
28
+ end
29
+
30
+ def request(req, body = nil, &block) # :yield: +response+
31
+ unless started?
32
+ start {
33
+ req['connection'] ||= 'close'
34
+ return request(req, body, &block)
35
+ }
36
+ end
37
+ if proxy_user()
38
+ unless use_ssl?
39
+ req.proxy_basic_auth proxy_user(), proxy_pass()
40
+ end
41
+ end
42
+
43
+ req.set_body_internal body
44
+ begin_transport req
45
+ req.exec @socket, @curr_http_version, edit_path(req.path)
46
+ begin
47
+ res = HTTPResponse.read_new(@socket)
48
+ end while res.kind_of?(HTTPContinue)
49
+ # If proxy was specified, and the server is demanding authentication, negotiate with it
50
+ if res.kind_of?(HTTPProxyAuthenticationRequired) && proxy? && res["Proxy-Authenticate"].include?("Negotiate")
51
+ begin
52
+ n = Win32::SSPI::NegotiateAuth.new
53
+ res.reading_body(@socket, req.response_body_permitted?) { }
54
+ end_transport req, res
55
+ begin_transport req
56
+ req["Proxy-Authorization"] = "Negotiate #{n.get_initial_token}"
57
+ # Some versions of ISA will close the connection if this isn't present.
58
+ req["Connection"] = "Keep-Alive"
59
+ req["Proxy-Connection"] = "Keep-Alive"
60
+ req.exec @socket, @curr_http_version, edit_path(req.path)
61
+ begin
62
+ res = HTTPResponse.read_new(@socket)
63
+ end while res.kind_of?(HTTPContinue)
64
+ if res["Proxy-Authenticate"]
65
+ res.reading_body(@socket, req.response_body_permitted?) { }
66
+ req["Proxy-Authorization"] = "Negotiate #{n.complete_authentication res["Proxy-Authenticate"]}"
67
+ req["Connection"] = "Keep-Alive"
68
+ req["Proxy-Connection"] = "Keep-Alive"
69
+ req.exec @socket, @curr_http_version, edit_path(req.path)
70
+ begin
71
+ res = HTTPResponse.read_new(@socket)
72
+ end while res.kind_of?(HTTPContinue)
73
+ end
74
+ rescue
75
+ exc = $!.exception("Error occurred during proxy negotiation. socket.io: #{@socket.inspect}; socket.closed? #{@socket.closed?}; req: #{req.inspect}; res: #{res.inspect}; Original message: #{$!.message}")
76
+ exc.set_backtrace $!.backtrace
77
+ raise exc
78
+ end
79
+ end
80
+ res.reading_body(@socket, req.response_body_permitted?) {
81
+ yield res if block_given?
82
+ }
83
+ end_transport req, res
84
+
85
+ res
86
+ end
87
+ end
88
+ end
data/spa.rb ADDED
@@ -0,0 +1,19 @@
1
+ # Copy this file to site_ruby directory to enable inclusion of Negotiate authoritation on arbitrary scripts.
2
+ # For example, to run gems w/ the library use:
3
+ #
4
+ # ruby -rspa 'C:\Program Files\ruby\bin\gem'
5
+ #
6
+ # Notice the gem script is executed directly, instead of executing the gem.cmd file.
7
+ #
8
+
9
+ require 'pathname'
10
+ if (Pathname.new(__FILE__).dirname + "/lib/sspi.rb").exist?
11
+ # If running directly from root of gem, load rubysspi directly
12
+ $: << (Pathname.new(__FILE__).dirname + "/lib").to_s
13
+ require 'win32/sspi/http_proxy_patch'
14
+ else
15
+ # Production require - must be running from site_ruby. Use rubygems to get
16
+ # to rubysspi
17
+ require 'rubygems'
18
+ require 'win32/sspi/http_proxy_patch'
19
+ end
@@ -0,0 +1,44 @@
1
+ #
2
+ # = test/test_gem_list.rb
3
+ #
4
+ # Copyright (c) 2006-2007 Justin Bailey
5
+ #
6
+ # Written and maintained by Justin Bailey <jgbailey@gmail.com>.
7
+ #
8
+ # This program is free software. You can re-distribute and/or
9
+ # modify this program under the same terms of ruby itself ---
10
+ # Ruby Distribution License or GNU General Public License.
11
+ #
12
+
13
+ # Magic constant will ensure that, if the SSPI patch has been applied, it won't break these tests
14
+ DISABLE_RUBY_SSPI_PATCH = true
15
+
16
+ require 'test/unit'
17
+ require 'net/http'
18
+ require 'rubygems'
19
+ $: << (File.dirname(__FILE__) << "/../lib")
20
+ require 'win32/sspi/http_proxy_patch'
21
+
22
+ class NTLMTest < Test::Unit::TestCase
23
+ def setup
24
+ assert ENV["http_proxy"], "http_proxy must be set before running tests."
25
+ end
26
+
27
+ # Previous implementation of rubysspi used dl/win32 and a
28
+ # bug occurred when gem list was executed. This tests to ensure
29
+ # bug does not come back.
30
+ def test_gem_list
31
+ Gem.manage_gems
32
+
33
+ if Gem::Version.new(Gem::RubyGemsVersion) < Gem::Version.new("0.9.2")
34
+ assert_nothing_raised "'gem list --remote rubysspi' failed to execute" do
35
+ Gem::GemRunner.new.run(%w(list rubysspi --remote --http-proxy ))
36
+ end
37
+ elsif Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new("0.9.2")
38
+ # --http-proxy option not needed after 0.9.2 (at least)
39
+ assert_nothing_raised "'gem list --remote rubysspi' failed to execute" do
40
+ Gem::GemRunner.new.run(%w(list rubysspi --remote))
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,46 @@
1
+ #
2
+ # = test/test_net_http.rb
3
+ #
4
+ # Copyright (c) 2006-2007 Justin Bailey
5
+ #
6
+ # Written and maintained by Justin Bailey <jgbailey@gmail.com>.
7
+ #
8
+ # This program is free software. You can re-distribute and/or
9
+ # modify this program under the same terms of ruby itself ---
10
+ # Ruby Distribution License or GNU General Public License.
11
+ #
12
+
13
+ DISABLE_RUBY_SSPI_PATCH = true
14
+
15
+ require 'test/unit'
16
+ require 'net/http'
17
+ require 'pathname'
18
+ $: << (File.dirname(__FILE__) << "/../lib")
19
+ require 'win32/sspi/http_proxy_patch'
20
+
21
+ class NTLMTest < Test::Unit::TestCase
22
+ def test_net_http
23
+ proxy = get_proxy
24
+ Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
25
+ resp = http.get("/")
26
+ assert resp.code.to_i == 200, "Did not get response from Google as expected."
27
+ end
28
+ end
29
+
30
+ def test_head_request
31
+ proxy = get_proxy
32
+ Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
33
+ resp = http.head("/")
34
+ assert resp.code.to_i == 200, "Did not get response from Google as expected."
35
+ end
36
+
37
+ end
38
+
39
+ def get_proxy
40
+ assert ENV["http_proxy"], "http_proxy environment variable must be set."
41
+ proxy = URI.parse(ENV["http_proxy"])
42
+ assert proxy.host && proxy.port, "Could not parse http_proxy (#{ENV["http_proxy"]}). http_proxy should be a URL with a port (e.g. http://proxy.corp.com:8080)."
43
+
44
+ return proxy
45
+ end
46
+ end
@@ -0,0 +1,37 @@
1
+ #
2
+ # = test/test_patched_net_http.rb
3
+ #
4
+ # Copyright (c) 2006-2007 Justin Bailey
5
+ #
6
+ # Written and maintained by Justin Bailey <jgbailey@gmail.com>.
7
+ #
8
+ # This program is free software. You can re-distribute and/or
9
+ # modify this program under the same terms of ruby itself ---
10
+ # Ruby Distribution License or GNU General Public License.
11
+ #
12
+
13
+ require 'test/unit'
14
+ require 'net/http'
15
+
16
+ # Intended to test 'patched' version of Net::HTTP. Notice lack of requires at the top.
17
+ class PatchedRubyTest < Test::Unit::TestCase
18
+ def setup
19
+ assert ENV["http_proxy"], "http_proxy must be set before running tests."
20
+ end
21
+
22
+ def test_net_http
23
+
24
+ assert_nothing_raised "net/http does not appear to be patched" do
25
+ assert Net::HTTP.sspi?, "sspi? patch applied but did not return true."
26
+ end
27
+
28
+ assert ENV["http_proxy"], "http_proxy environment variable must be set."
29
+ proxy = URI.parse(ENV["http_proxy"])
30
+ assert proxy.host && proxy.port, "Could not parse http_proxy (#{ENV["http_proxy"]}). http_proxy should be a URL with a port (e.g. http://proxy.corp.com:8080)."
31
+
32
+ Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
33
+ resp = http.get("/")
34
+ assert resp.code.to_i == 200, "Did not get response from Google as expected."
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,99 @@
1
+ #
2
+ # = test/test_ruby_sspi.rb
3
+ #
4
+ # Copyright (c) 2006-2007 Justin Bailey
5
+ #
6
+ # Written and maintained by Justin Bailey <jgbailey@gmail.com>.
7
+ #
8
+ # This program is free software. You can re-distribute and/or
9
+ # modify this program under the same terms of ruby itself ---
10
+ # Ruby Distribution License or GNU General Public License.
11
+ #
12
+
13
+ # Magic constant will ensure that, if the SSPI patch has been applied, it won't break these tests
14
+ DISABLE_RUBY_SSPI_PATCH = true
15
+
16
+ require 'test/unit'
17
+ require 'net/http'
18
+ require 'pathname'
19
+ $: << (File.dirname(__FILE__) << "/../lib")
20
+ require 'win32/sspi'
21
+
22
+ class NTLMTest < Test::Unit::TestCase
23
+ def test_auth
24
+ proxy = get_proxy
25
+
26
+ Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
27
+ nego_auth = Win32::SSPI::NegotiateAuth.new
28
+ sr = http.request_get "/", { "Proxy-Authorization" => "Negotiate " + nego_auth.get_initial_token }
29
+ resp = http.get "/", { "Proxy-Authorization" => "Negotiate " + nego_auth.complete_authentication(sr["Proxy-Authenticate"].split(" ").last.strip) }
30
+ # Google redirects to country of origins domain if not US.
31
+ assert success_or_redirect(resp.code), "Response code not as expected: #{resp.inspect}"
32
+ resp = http.get "/foobar.html"
33
+ # Some proxy servers don't return 404 but 407.
34
+ assert(resp.code.to_i == 404 || resp.code.to_i == 407, "Response code not as expected: #{resp.inspect}")
35
+ end
36
+ end
37
+
38
+ def test_proxy_auth_get
39
+ proxy = get_proxy
40
+
41
+ Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
42
+ resp = Win32::SSPI::NegotiateAuth.proxy_auth_get http, "/"
43
+ assert success_or_redirect(resp.code), "Response code not as expected: #{resp.inspect}"
44
+ end
45
+ end
46
+
47
+ def test_one_time_use_only
48
+ proxy = get_proxy
49
+
50
+ Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
51
+ nego_auth = Win32::SSPI::NegotiateAuth.new
52
+ sr = http.request_get "/", { "Proxy-Authorization" => "Negotiate " + nego_auth.get_initial_token }
53
+ resp = http.get "/", { "Proxy-Authorization" => "Negotiate " + nego_auth.complete_authentication(sr["Proxy-Authenticate"].split(" ").last.strip) }
54
+ assert success_or_redirect(resp.code), "Response code not as expected: #{resp.inspect}"
55
+ assert_raises(RuntimeError, "Should not be able to call complete_authentication again") do
56
+ nego_auth.complete_authentication "foo"
57
+ end
58
+ end
59
+ end
60
+
61
+ def test_token_variations
62
+ proxy = get_proxy
63
+
64
+ # Test that raw token works
65
+ Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
66
+ nego_auth = Win32::SSPI::NegotiateAuth.new
67
+ sr = http.request_get "/", { "Proxy-Authorization" => "Negotiate " + nego_auth.get_initial_token }
68
+ token = Base64.decode64(sr["Proxy-Authenticate"].split(" ").last.strip)
69
+ completed_token = nego_auth.complete_authentication(token)
70
+ resp = http.get "/", { "Proxy-Authorization" => "Negotiate " + completed_token }
71
+ assert success_or_redirect(resp.code), "Response code not as expected: #{resp.inspect}"
72
+ end
73
+
74
+ # Test that token w/ "Negotiate" header included works
75
+ Net::HTTP.Proxy(proxy.host, proxy.port).start("www.google.com") do |http|
76
+ nego_auth = Win32::SSPI::NegotiateAuth.new
77
+ sr = http.request_get "/", { "Proxy-Authorization" => "Negotiate " + nego_auth.get_initial_token }
78
+ resp = http.get "/", { "Proxy-Authorization" => "Negotiate " + nego_auth.complete_authentication(sr["Proxy-Authenticate"]) }
79
+ assert success_or_redirect(resp.code), "Response code not as expected: #{resp.inspect}"
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ # Gets the proxy from the environment and makes some assertions
86
+ def get_proxy
87
+ assert ENV["http_proxy"], "http_proxy environment variable must be set."
88
+ proxy = URI.parse(ENV["http_proxy"])
89
+ assert proxy.host && proxy.port, "Could not parse http_proxy (#{ENV["http_proxy"]}). http_proxy should be a URL with a port (e.g. http://proxy.corp.com:8080)."
90
+
91
+ return proxy
92
+ end
93
+
94
+ # Returns true if code given is 200 or 302. I.e. if HTTP request was successful or resulted in redirect.
95
+ def success_or_redirect(code)
96
+ code.to_i == 200 || code.to_i == 302
97
+ end
98
+
99
+ end
@@ -0,0 +1,39 @@
1
+ #
2
+ # = test/trace_proxy.rb
3
+ #
4
+ # Copyright (c) 2006-2007 Justin Bailey
5
+ #
6
+ # Written and maintained by Justin Bailey <jgbailey@gmail.com>.
7
+ #
8
+ # This program is free software. You can re-distribute and/or
9
+ # modify this program under the same terms of ruby itself ---
10
+ # Ruby Distribution License or GNU General Public License.
11
+ #
12
+
13
+ # This file provides a trace of the HTTP request/response sequence between the local computer and the proxy. Useful
14
+ # for debugging problems with the library. Outputs trace to standard output.
15
+
16
+ # Magic constant will ensure that, if the SSPI patch has been applied, it won't break these tests
17
+ DISABLE_RUBY_SSPI_PATCH = true
18
+
19
+ require 'net/http'
20
+ require 'pathname'
21
+ $: << (File.dirname(__FILE__) << "/../lib")
22
+ require 'win32/sspi'
23
+
24
+ raise "http_proxy environment variable must be set." unless ENV["http_proxy"]
25
+ proxy = URI.parse(ENV["http_proxy"])
26
+ raise "Could not parse http_proxy (#{ENV["http_proxy"]}). http_proxy should be a URL with a port (e.g. http://proxy.corp.com:8080)." unless proxy.host && proxy.port
27
+
28
+ conn = Net::HTTP.Proxy(proxy.host, proxy.port).new("www.google.com")
29
+ conn.set_debug_output $stdout
30
+ conn.start() do |http|
31
+ nego_auth = Win32::SSPI::NegotiateAuth.new
32
+ sr = http.request_get "/", { "Proxy-Authorization" => "Negotiate " + nego_auth.get_initial_token }
33
+ resp = http.get "/", { "Proxy-Authorization" => "Negotiate " + nego_auth.complete_authentication(sr["Proxy-Authenticate"].split(" ").last.strip) }
34
+ # Google redirects to country of origins domain if not US.
35
+ raise "Response code not as expected: #{resp.inspect}" unless resp.code.to_i == 200 || resp.code.to_i == 302
36
+ resp = http.get "/foobar.html"
37
+ # Some proxy servers don't return 404 but 407.
38
+ raise "Response code not as expected: #{resp.inspect}" unless resp.code.to_i == 404 || resp.code.to_i == 407
39
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.2
3
+ specification_version: 1
4
+ name: rubysspi
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.2.2
7
+ date: 2007-03-12 00:00:00 -07:00
8
+ summary: A library which implements Ruby bindings to the Win32 SSPI library. Also includes a module to add Negotiate authentication support to Net::HTTP.
9
+ require_paths:
10
+ - lib
11
+ email: jgbailey @nospam@ gmail.com
12
+ homepage: http://rubyforge.org/projects/rubysspi/
13
+ rubyforge_project: http://rubyforge.org/projects/rubysspi/
14
+ description: This gem provides bindings to the Win32 SSPI libraries, primarily to support Negotiate (i.e. SPNEGO, NTLM) authentication with a proxy server. Enough support is implemented to provide the necessary support for the authentication. A module is also provided which overrides Net::HTTP and adds support for Negotiate authentication to it. This implies that open-uri automatically gets support for it, as long as the http_proxy environment variable is set.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Justin Bailey
31
+ files:
32
+ - lib/win32
33
+ - lib/win32/sspi
34
+ - lib/win32/sspi.rb
35
+ - lib/win32/sspi/http_proxy_patch.rb
36
+ - test/test_gem_list.rb
37
+ - test/test_net_http.rb
38
+ - test/test_patched_net_http.rb
39
+ - test/test_ruby_sspi.rb
40
+ - test/trace_proxy.rb
41
+ - CHANGELOG.txt
42
+ - LICENSE.txt
43
+ - README.txt
44
+ - Rakefile
45
+ - spa.rb
46
+ test_files: []
47
+
48
+ rdoc_options:
49
+ - --title
50
+ - Ruby SSPI -- Win32 SSPI Bindings for Ruby
51
+ - --main
52
+ - README.txt
53
+ - --line-numbers
54
+ extra_rdoc_files:
55
+ - README.txt
56
+ - CHANGELOG.txt
57
+ - LICENSE.txt
58
+ executables:
59
+ - apply_sspi_patch
60
+ extensions: []
61
+
62
+ requirements: []
63
+
64
+ dependencies: []
65
+