winrm-s 0.2.4 → 0.3.0.dev.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,69 +1,69 @@
1
- winrm-s
2
- =======
3
-
4
- `winrm-s` extends the functionality of the
5
- [WinRM gem](http://rubygems.org/gems/winrm) to support the Microsoft
6
- [Negotiate protocol](http://msdn.microsoft.com/en-us/library/windows/desktop/aa378748(v=vs.85).aspx)
7
- when authenticating to a remote [WinRM](http://msdn.microsoft.com/en-us/library/aa384426(v=vs.85).aspx) endpoint from a Windows system.
8
-
9
- This extended functionality is **only** supported when running on Microsoft
10
- Windows. This gem can still be used on other operating systems just like the
11
- `WinRM` gem, but the extended capabilities will not be available.
12
-
13
- Installation
14
- ------------
15
-
16
- To install it, run:
17
-
18
- gem install winrm-s
19
-
20
- Usage
21
- -----
22
-
23
- `winrm-s` provides the same interface as the `winrm` gem -- see `winrm`
24
- [documentation](https://github.com/WinRb/WinRM/blob/master/README.md) for `winrm-s` usage.
25
-
26
- * To use it, simply require `winrm` or `winrm-s`, depending on whether your code
27
- is running on Windows. The extended negotiate protocol is only available if
28
- you include `winrm-s`, which will only work on Windows.
29
- * When you use WinRM::WinRMWebService.new, be sure to specify the
30
- `:sspinegotiate` parameter, along with a user name in the form `domain_name\user_name`
31
- for the user name in order to make use of negotiate protocol. If the user
32
- account is local to the remote system, you can use `.` for the domain. The
33
- example further on demonstrates the negotiate use case.
34
- * All other use cases enabled by the `winrm` gem are also supported.
35
-
36
- Example
37
- -------
38
- Note the argument value of `:sspinegotiate` for transport option, and the
39
- explicit specification of a domain name, in this case `.`, in the user name:
40
- ```ruby
41
- if RUBY_PLATFORM =~ /mswin|mingw32|windows/
42
- require 'winrm-s' # only works on Windows, otherwise use require 'winrm'
43
- endpoint = http://mywinrmhost:5985/wsman
44
- winrm = WinRM::WinRMWebService.new(endpoint, :sspinegotiate, :user => ".\administrator", :pass => "adminpasswd")
45
- winrm.cmd('ipconfig /all') do |stdout, stderr|
46
- STDOUT.print stdout
47
- STDERR.print stderr
48
- end
49
- end
50
- ```
51
-
52
- License
53
- -------
54
-
55
- Copyright:: Copyright (c) 2014 Chef Software, Inc.
56
- License:: Apache License, Version 2.0
57
-
58
- Licensed under the Apache License, Version 2.0 (the "License");
59
- you may not use this file except in compliance with the License.
60
- You may obtain a copy of the License at
61
-
62
- http://www.apache.org/licenses/LICENSE-2.0
63
-
64
- Unless required by applicable law or agreed to in writing, software
65
- distributed under the License is distributed on an "AS IS" BASIS,
66
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
67
- See the License for the specific language governing permissions and
68
- limitations under the License.
69
-
1
+ winrm-s
2
+ =======
3
+
4
+ `winrm-s` extends the functionality of the
5
+ [WinRM gem](http://rubygems.org/gems/winrm) to support the Microsoft
6
+ [Negotiate protocol](http://msdn.microsoft.com/en-us/library/windows/desktop/aa378748(v=vs.85).aspx)
7
+ when authenticating to a remote [WinRM](http://msdn.microsoft.com/en-us/library/aa384426(v=vs.85).aspx) endpoint from a Windows system.
8
+
9
+ This extended functionality is **only** supported when running on Microsoft
10
+ Windows. This gem can still be used on other operating systems just like the
11
+ `WinRM` gem, but the extended capabilities will not be available.
12
+
13
+ Installation
14
+ ------------
15
+
16
+ To install it, run:
17
+
18
+ gem install winrm-s
19
+
20
+ Usage
21
+ -----
22
+
23
+ `winrm-s` provides the same interface as the `winrm` gem -- see `winrm`
24
+ [documentation](https://github.com/WinRb/WinRM/blob/master/README.md) for `winrm-s` usage.
25
+
26
+ * To use it, simply require `winrm` or `winrm-s`, depending on whether your code
27
+ is running on Windows. The extended negotiate protocol is only available if
28
+ you include `winrm-s`, which will only work on Windows.
29
+ * When you use WinRM::WinRMWebService.new, be sure to specify the
30
+ `:sspinegotiate` parameter, along with a user name in the form `domain_name\user_name`
31
+ for the user name in order to make use of negotiate protocol. If the user
32
+ account is local to the remote system, you can use `.` for the domain. The
33
+ example further on demonstrates the negotiate use case.
34
+ * All other use cases enabled by the `winrm` gem are also supported.
35
+
36
+ Example
37
+ -------
38
+ Note the argument value of `:sspinegotiate` for transport option, and the
39
+ explicit specification of a domain name, in this case `.`, in the user name:
40
+ ```ruby
41
+ if RUBY_PLATFORM =~ /mswin|mingw32|windows/
42
+ require 'winrm-s' # only works on Windows, otherwise use require 'winrm'
43
+ endpoint = http://mywinrmhost:5985/wsman
44
+ winrm = WinRM::WinRMWebService.new(endpoint, :sspinegotiate, :user => ".\administrator", :pass => "adminpasswd")
45
+ winrm.cmd('ipconfig /all') do |stdout, stderr|
46
+ STDOUT.print stdout
47
+ STDERR.print stderr
48
+ end
49
+ end
50
+ ```
51
+
52
+ License
53
+ -------
54
+
55
+ Copyright:: Copyright (c) 2014 Chef Software, Inc.
56
+ License:: Apache License, Version 2.0
57
+
58
+ Licensed under the Apache License, Version 2.0 (the "License");
59
+ you may not use this file except in compliance with the License.
60
+ You may obtain a copy of the License at
61
+
62
+ http://www.apache.org/licenses/LICENSE-2.0
63
+
64
+ Unless required by applicable law or agreed to in writing, software
65
+ distributed under the License is distributed on an "AS IS" BASIS,
66
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
67
+ See the License for the specific language governing permissions and
68
+ limitations under the License.
69
+
data/Rakefile CHANGED
@@ -1 +1 @@
1
- require 'bundler/gem_tasks'
1
+ require 'bundler/gem_tasks'
data/lib/winrm-s.rb CHANGED
@@ -1,30 +1,30 @@
1
- #
2
- # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)>
3
- # Copyright:: Copyright (c) 2014 Chef Software, Inc.
4
- # License:: Apache License, Version 2.0
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
- #
18
-
19
- require 'winrm'
20
-
21
- def windows?
22
- !!(RUBY_PLATFORM =~ /mswin|mingw|windows/)
23
- end
24
-
25
- # Patch only on windows
26
- if windows?
27
- require 'winrm/winrm_service_patch'
28
- else
29
- raise "ERROR: winrm-s extensions to the winrm gem for the negotiate protocol are only supported on Windows. Require 'winrm' and not 'winrm-s' on non-Windows platforms."
30
- end
1
+ #
2
+ # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)>
3
+ # Copyright:: Copyright (c) 2014 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'winrm'
20
+
21
+ def windows?
22
+ !!(RUBY_PLATFORM =~ /mswin|mingw|windows/)
23
+ end
24
+
25
+ # Patch only on windows
26
+ if windows?
27
+ require 'winrm/winrm_service_patch'
28
+ else
29
+ raise "ERROR: winrm-s extensions to the winrm gem for the negotiate protocol are only supported on Windows. Require 'winrm' and not 'winrm-s' on non-Windows platforms."
30
+ end
@@ -1,22 +1,22 @@
1
- #
2
- # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
3
- # Copyright:: Copyright (c) 2014 Chef Software, Inc.
4
- # License:: Apache License, Version 2.0
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
- #
18
-
19
- module WinrmS
20
- VERSION = "0.2.4"
21
- MAJOR, MINOR, TINY = VERSION.split('.')
22
- end
1
+ #
2
+ # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
3
+ # Copyright:: Copyright (c) 2014 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ module WinrmS
20
+ VERSION = "0.3.0.dev.0"
21
+ MAJOR, MINOR, TINY = VERSION.split('.')
22
+ end
@@ -1,18 +1,18 @@
1
-
2
- module PatchAssertions
3
-
4
- def self.assert_major_version(library_name, expected_major, override_var_name)
5
- supported_major_ver = ENV[override_var_name] || expected_major
6
- loaded_ver = Gem.loaded_specs[library_name].version
7
- loaded_major_ver = loaded_ver.to_s.match(/(^\d+\.\d+)\.(.*)$/)[1]
8
- if loaded_major_ver.to_s != supported_major_ver.to_s
9
- puts "WARNING: Unsupported version of #{library_name}. The supported major version of library is #{library_name} version #{expected_major}. This code path monkey patches few methods in #{library_name} to support additional features. This could possibly work, but it is advised to extensively test your version. If you are aware of the impact of using #{loaded_ver}, this warning can be disabled by setting #{override_var_name} to the major version #{loaded_major_ver}. "
10
- end
11
- end
12
-
13
- def self.assert_arity_of_patched_method(klass, method_name, expected_arity)
14
- if klass.instance_method(method_name).arity != expected_arity
15
- puts "WARNING: Cannot patch method #{klass}::#{method_name} since your latest gem seems to have different method definition that cannot be safely patched. This could possibly work, but it is advised to extensively test your version. Please use the supported version of patched gem."
16
- end
17
- end
1
+
2
+ module PatchAssertions
3
+
4
+ def self.assert_major_version(library_name, expected_major, override_var_name)
5
+ supported_major_ver = ENV[override_var_name] || expected_major
6
+ loaded_ver = Gem.loaded_specs[library_name].version
7
+ loaded_major_ver = loaded_ver.to_s.match(/(^\d+\.\d+)\.(.*)$/)[1]
8
+ if loaded_major_ver.to_s != supported_major_ver.to_s
9
+ puts "WARNING: Unsupported version of #{library_name}. The supported major version of library is #{library_name} version #{expected_major}. This code path monkey patches few methods in #{library_name} to support additional features. This could possibly work, but it is advised to extensively test your version. If you are aware of the impact of using #{loaded_ver}, this warning can be disabled by setting #{override_var_name} to the major version #{loaded_major_ver}. "
10
+ end
11
+ end
12
+
13
+ def self.assert_arity_of_patched_method(klass, method_name, expected_arity)
14
+ if klass.instance_method(method_name).arity != expected_arity
15
+ puts "WARNING: Cannot patch method #{klass}::#{method_name} since your latest gem seems to have different method definition that cannot be safely patched. This could possibly work, but it is advised to extensively test your version. Please use the supported version of patched gem."
16
+ end
17
+ end
18
18
  end
@@ -1,191 +1,191 @@
1
- #
2
- # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
3
- # Copyright:: Copyright (c) 2014 Chef Software, Inc.
4
- # License:: Apache License, Version 2.0
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
- #
18
-
19
- require 'httpclient/auth'
20
-
21
- require 'winrm/helpers/assert_patch'
22
-
23
- def validate_patch
24
- # The code below patches the httpclient library to add support
25
- # for encrypt/decrypt as described below.
26
- # Add few restriction to make sure the patched methods are still
27
- # available, but still give a way to consciously use later versions
28
- PatchAssertions.assert_major_version("httpclient", 2.6, "USE_HTTPCLIENT_MAJOR")
29
- PatchAssertions.assert_arity_of_patched_method(HTTPClient::WWWAuth, "filter_request", 1)
30
- PatchAssertions.assert_arity_of_patched_method(HTTPClient::WWWAuth, "filter_response", 2)
31
- PatchAssertions.assert_arity_of_patched_method(HTTPClient::SSPINegotiateAuth, "set", -1)
32
- PatchAssertions.assert_arity_of_patched_method(HTTPClient::SSPINegotiateAuth, "get", 1)
33
- end
34
-
35
- # Perform the patch validations
36
- validate_patch
37
-
38
- # Overrides the HTTPClient::WWWAuth code to add support for encryption/decryption
39
- # of data sent during the NTLM auth over negotiate.
40
-
41
- # Also Overrides HTTPClient::SSPINegotiateAuth to remember user credentials, original
42
- # library code relies on the current login user credentials on the client machine.
43
-
44
- # Below code helps ruby client to perform auth using the credentials provided to
45
- # ruby client, and also enhances to use encrypted channel.
46
-
47
- class HTTPClient
48
-
49
- class WWWAuth
50
-
51
- # Filter API implementation. Traps HTTP request and insert
52
- # 'Authorization' header if needed.
53
- def filter_request(req)
54
- @authenticator.each do |auth|
55
- next unless auth.set? # hasn't be set, don't use it
56
- if cred = auth.get(req)
57
- auth.encrypt_payload(req) if auth.respond_to?(:encrypt_payload)
58
- req.header.set('Authorization', auth.scheme + " " + cred)
59
- return
60
- end
61
- end
62
- end
63
-
64
- # Filter API implementation. Traps HTTP response and parses
65
- # 'WWW-Authenticate' header.
66
- #
67
- # This remembers the challenges for all authentication methods
68
- # available to the client. On the subsequent retry of the request,
69
- # filter_request will select the strongest method.
70
- def filter_response(req, res)
71
- command = nil
72
- if res.status == HTTP::Status::UNAUTHORIZED
73
- if challenge = parse_authentication_header(res, 'www-authenticate')
74
- uri = req.header.request_uri
75
- challenge.each do |scheme, param_str|
76
- @authenticator.each do |auth|
77
- next unless auth.set? # hasn't be set, don't use it
78
- if scheme.downcase == auth.scheme.downcase
79
- challengeable = auth.challenge(uri, param_str)
80
- command = :retry if challengeable
81
- end
82
- end
83
- end
84
- # ignore unknown authentication scheme
85
- end
86
- elsif res.status == HTTP::Status::OK
87
- decrypted_content = res.content
88
- @authenticator.each do |auth|
89
- next unless auth.set? # hasn't be set, don't use it
90
- decrypted_content = auth.decrypt_payload(res.content) if auth.respond_to?(:encrypted_channel?) and auth.encrypted_channel?
91
- end
92
- # update with decrypted content
93
- res.content.replace(decrypted_content) if res.content and !res.content.empty?
94
- end
95
- command
96
- end
97
- end
98
-
99
- class SSPINegotiateAuth
100
-
101
- begin
102
- require 'win32/sspi'
103
- SSPIEnabled = true
104
- rescue LoadError
105
- SSPIEnabled = false
106
- end
107
-
108
- begin
109
- require 'gssapi'
110
- GSSAPIEnabled = true
111
- rescue LoadError
112
- GSSAPIEnabled = false
113
- end
114
-
115
- # Override to remember creds
116
- # Set authentication credential.
117
- def set(uri, user, passwd)
118
- # Check if user has domain specified in it.
119
- if user
120
- creds = user.split("\\")
121
- creds.length.eql?(2) ? (@domain,@user = creds) : @user = creds[0]
122
- end
123
- @passwd = passwd
124
- end
125
-
126
- def set?
127
- SSPIEnabled || GSSAPIEnabled
128
- end
129
-
130
- # Response handler: returns credential.
131
- # See win32/sspi for negotiation state transition.
132
- def get(req)
133
- return nil unless SSPIEnabled || GSSAPIEnabled
134
- target_uri = req.header.request_uri
135
- domain_uri, param = @challenge.find { |uri, v|
136
- Util.uri_part_of(target_uri, uri)
137
- }
138
-
139
- return nil unless param
140
-
141
- state = param[:state]
142
- authenticator = param[:authenticator]
143
- authphrase = param[:authphrase]
144
- case state
145
- when :init
146
- if SSPIEnabled
147
- # Over-ride ruby win32 sspi to support encrypt/decrypt
148
- require 'winrm/win32/sspi'
149
- authenticator = param[:authenticator] = Win32::SSPI::NegotiateAuth.new(@user, @domain, @passwd)
150
- @authenticator = authenticator # **** Hacky remember as we need this for encrypt/decrypt
151
- return authenticator.get_initial_token
152
- else # use GSSAPI
153
- authenticator = param[:authenticator] = GSSAPI::Simple.new(domain_uri.host, 'HTTP')
154
- # Base64 encode the context token
155
- return [authenticator.init_context].pack('m').gsub(/\n/,'')
156
- end
157
- when :response
158
- @challenge.delete(domain_uri)
159
- if SSPIEnabled
160
- return authenticator.complete_authentication(authphrase)
161
- else # use GSSAPI
162
- return authenticator.init_context(authphrase.unpack('m').pop)
163
- end
164
- end
165
- nil
166
- end
167
-
168
- def encrypted_channel?
169
- @encrypted_channel
170
- end
171
-
172
- def encrypt_payload(req)
173
- if SSPIEnabled
174
- body = @authenticator.encrypt_payload(req.body)
175
- req.http_body = HTTP::Message::Body.new
176
- req.http_body.init_request(body)
177
- req.http_header.body_size = body.length if body
178
- # if body is encrypted update the header
179
- if body.include? "HTTP-SPNEGO-session-encrypted"
180
- @encrypted_channel = true
181
- req.header.set('Content-Type', "multipart/encrypted;protocol=\"application/HTTP-SPNEGO-session-encrypted\";boundary=\"Encrypted Boundary\"")
182
- end
183
- end
184
- end
185
-
186
- def decrypt_payload(body)
187
- body = @authenticator.decrypt_payload(body) if SSPIEnabled
188
- body
189
- end
190
- end
191
- end
1
+ #
2
+ # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
3
+ # Copyright:: Copyright (c) 2014 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'httpclient/auth'
20
+
21
+ require 'winrm/helpers/assert_patch'
22
+
23
+ def validate_patch
24
+ # The code below patches the httpclient library to add support
25
+ # for encrypt/decrypt as described below.
26
+ # Add few restriction to make sure the patched methods are still
27
+ # available, but still give a way to consciously use later versions
28
+ PatchAssertions.assert_major_version("httpclient", 2.6, "USE_HTTPCLIENT_MAJOR")
29
+ PatchAssertions.assert_arity_of_patched_method(HTTPClient::WWWAuth, "filter_request", 1)
30
+ PatchAssertions.assert_arity_of_patched_method(HTTPClient::WWWAuth, "filter_response", 2)
31
+ PatchAssertions.assert_arity_of_patched_method(HTTPClient::SSPINegotiateAuth, "set", -1)
32
+ PatchAssertions.assert_arity_of_patched_method(HTTPClient::SSPINegotiateAuth, "get", 1)
33
+ end
34
+
35
+ # Perform the patch validations
36
+ validate_patch
37
+
38
+ # Overrides the HTTPClient::WWWAuth code to add support for encryption/decryption
39
+ # of data sent during the NTLM auth over negotiate.
40
+
41
+ # Also Overrides HTTPClient::SSPINegotiateAuth to remember user credentials, original
42
+ # library code relies on the current login user credentials on the client machine.
43
+
44
+ # Below code helps ruby client to perform auth using the credentials provided to
45
+ # ruby client, and also enhances to use encrypted channel.
46
+
47
+ class HTTPClient
48
+
49
+ class WWWAuth
50
+
51
+ # Filter API implementation. Traps HTTP request and insert
52
+ # 'Authorization' header if needed.
53
+ def filter_request(req)
54
+ @authenticator.each do |auth|
55
+ next unless auth.set? # hasn't be set, don't use it
56
+ if cred = auth.get(req)
57
+ auth.encrypt_payload(req) if auth.respond_to?(:encrypt_payload)
58
+ req.header.set('Authorization', auth.scheme + " " + cred)
59
+ return
60
+ end
61
+ end
62
+ end
63
+
64
+ # Filter API implementation. Traps HTTP response and parses
65
+ # 'WWW-Authenticate' header.
66
+ #
67
+ # This remembers the challenges for all authentication methods
68
+ # available to the client. On the subsequent retry of the request,
69
+ # filter_request will select the strongest method.
70
+ def filter_response(req, res)
71
+ command = nil
72
+ if res.status == HTTP::Status::UNAUTHORIZED
73
+ if challenge = parse_authentication_header(res, 'www-authenticate')
74
+ uri = req.header.request_uri
75
+ challenge.each do |scheme, param_str|
76
+ @authenticator.each do |auth|
77
+ next unless auth.set? # hasn't be set, don't use it
78
+ if scheme.downcase == auth.scheme.downcase
79
+ challengeable = auth.challenge(uri, param_str)
80
+ command = :retry if challengeable
81
+ end
82
+ end
83
+ end
84
+ # ignore unknown authentication scheme
85
+ end
86
+ elsif res.status == HTTP::Status::OK
87
+ decrypted_content = res.content
88
+ @authenticator.each do |auth|
89
+ next unless auth.set? # hasn't be set, don't use it
90
+ decrypted_content = auth.decrypt_payload(res.content) if auth.respond_to?(:encrypted_channel?) and auth.encrypted_channel?
91
+ end
92
+ # update with decrypted content
93
+ res.content.replace(decrypted_content) if res.content and !res.content.empty?
94
+ end
95
+ command
96
+ end
97
+ end
98
+
99
+ class SSPINegotiateAuth
100
+
101
+ begin
102
+ require 'win32/sspi'
103
+ SSPIEnabled = true
104
+ rescue LoadError
105
+ SSPIEnabled = false
106
+ end
107
+
108
+ begin
109
+ require 'gssapi'
110
+ GSSAPIEnabled = true
111
+ rescue LoadError
112
+ GSSAPIEnabled = false
113
+ end
114
+
115
+ # Override to remember creds
116
+ # Set authentication credential.
117
+ def set(uri, user, passwd)
118
+ # Check if user has domain specified in it.
119
+ if user
120
+ creds = user.split("\\")
121
+ creds.length.eql?(2) ? (@domain,@user = creds) : @user = creds[0]
122
+ end
123
+ @passwd = passwd
124
+ end
125
+
126
+ def set?
127
+ SSPIEnabled || GSSAPIEnabled
128
+ end
129
+
130
+ # Response handler: returns credential.
131
+ # See win32/sspi for negotiation state transition.
132
+ def get(req)
133
+ return nil unless SSPIEnabled || GSSAPIEnabled
134
+ target_uri = req.header.request_uri
135
+ domain_uri, param = @challenge.find { |uri, v|
136
+ Util.uri_part_of(target_uri, uri)
137
+ }
138
+
139
+ return nil unless param
140
+
141
+ state = param[:state]
142
+ authenticator = param[:authenticator]
143
+ authphrase = param[:authphrase]
144
+ case state
145
+ when :init
146
+ if SSPIEnabled
147
+ # Over-ride ruby win32 sspi to support encrypt/decrypt
148
+ require 'winrm/win32/sspi'
149
+ authenticator = param[:authenticator] = Win32::SSPI::NegotiateAuth.new(@user, @domain, @passwd)
150
+ @authenticator = authenticator # **** Hacky remember as we need this for encrypt/decrypt
151
+ return authenticator.get_initial_token
152
+ else # use GSSAPI
153
+ authenticator = param[:authenticator] = GSSAPI::Simple.new(domain_uri.host, 'HTTP')
154
+ # Base64 encode the context token
155
+ return [authenticator.init_context].pack('m').gsub(/\n/,'')
156
+ end
157
+ when :response
158
+ @challenge.delete(domain_uri)
159
+ if SSPIEnabled
160
+ return authenticator.complete_authentication(authphrase)
161
+ else # use GSSAPI
162
+ return authenticator.init_context(authphrase.unpack('m').pop)
163
+ end
164
+ end
165
+ nil
166
+ end
167
+
168
+ def encrypted_channel?
169
+ @encrypted_channel
170
+ end
171
+
172
+ def encrypt_payload(req)
173
+ if SSPIEnabled
174
+ body = @authenticator.encrypt_payload(req.body)
175
+ req.http_body = HTTP::Message::Body.new
176
+ req.http_body.init_request(body)
177
+ req.http_header.body_size = body.length if body
178
+ # if body is encrypted update the header
179
+ if body.include? "HTTP-SPNEGO-session-encrypted"
180
+ @encrypted_channel = true
181
+ req.header.set('Content-Type', "multipart/encrypted;protocol=\"application/HTTP-SPNEGO-session-encrypted\";boundary=\"Encrypted Boundary\"")
182
+ end
183
+ end
184
+ end
185
+
186
+ def decrypt_payload(body)
187
+ body = @authenticator.decrypt_payload(body) if SSPIEnabled
188
+ body
189
+ end
190
+ end
191
+ end