├── rakelib └── epoch.rake ├── Gemfile ├── Rakefile ├── .mailmap ├── BSDL ├── net-http-sspi.gemspec ├── README.md ├── .github └── workflows │ └── main.yml ├── COPYING └── lib └── net └── http └── sspi.rb /rakelib/epoch.rake: -------------------------------------------------------------------------------- 1 | task "build" => "date_epoch" 2 | 3 | task "date_epoch" do 4 | ENV["SOURCE_DATE_EPOCH"] = IO.popen(%W[git -C #{__dir__} log -1 --format=%ct], &:read).chomp 5 | end 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # Specify your gem's dependencies in win32-sspi.gemspec 6 | gemspec 7 | 8 | gem "rake", "~> 13.0" 9 | 10 | gem "test-unit" 11 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rake/testtask" 5 | 6 | Rake::TestTask.new(:test) do |t| 7 | t.libs << "test" 8 | t.libs << "lib" 9 | t.test_files = FileList["test/**/test_*.rb"] 10 | end 11 | 12 | task default: :test 13 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | # aamine 2 | Minero Aoki 3 | Minero Aoki 4 | 5 | # hsbt 6 | Hiroshi SHIBATA 7 | Hiroshi SHIBATA 8 | 9 | # naruse 10 | NARUSE, Yui 11 | NARUSE, Yui 12 | NARUSE, Yui 13 | 14 | # nobu 15 | Nobuyoshi Nakada 16 | Nobuyoshi Nakada 17 | 18 | # usa 19 | U.Nakamura 20 | U.Nakamura 21 | U.Nakamura 22 | -------------------------------------------------------------------------------- /BSDL: -------------------------------------------------------------------------------- 1 | Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /net-http-sspi.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | Gem::Specification.new do |spec| 3 | spec.name = "net-http-sspi" 4 | spec.version = "0.0.1" 5 | spec.authors = ["Justin Bailey"] 6 | spec.email = [""] 7 | 8 | spec.summary = %q{Windows SSPI implementation in Ruby} 9 | spec.description = %q{implements bindings to Win32 SSPI functions, focused on authentication to a proxy server over HTTP.} 10 | spec.licenses = %w[Ruby BSDL] 11 | spec.homepage = "https://github.com/ruby/net-http-sspi" 12 | spec.required_ruby_version = ">= 3.0.0" 13 | 14 | spec.metadata["homepage_uri"] = spec.homepage 15 | spec.metadata["source_code_uri"] = spec.homepage 16 | 17 | # Specify which files should be added to the gem when it is released. 18 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 19 | spec.files = Dir.chdir(__dir__) do 20 | `git ls-files -z`.split("\x0").reject do |f| 21 | (File.expand_path(f) == __FILE__) || 22 | f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile Rakefile]) || 23 | f.end_with?(*%w[.gemspec]) 24 | end 25 | end 26 | spec.bindir = "exe" 27 | spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } 28 | spec.require_paths = ["lib"] 29 | 30 | spec.add_dependency "fiddle", "~> 1.0" 31 | spec.add_dependency "net-http" 32 | end 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Net::HTTP::SSPI 2 | 3 | net-http-sspi implements bindings to Win32 SSPI functions, focused on 4 | authentication to a proxy server over HTTP. 5 | 6 | ## Installation 7 | 8 | net-http-sspi is a bundled gem of the ruby standard library, so that the 9 | latest version on the ruby release date is preinstalled on Windows. 10 | Other versions of the gem can be installed in addition like so: 11 | 12 | Install the gem and add to the application's Gemfile by executing: 13 | 14 | $ bundle add net-http-sspi 15 | 16 | If bundler is not being used to manage dependencies, install the gem 17 | by executing: 18 | 19 | $ gem install net-http-sspi 20 | 21 | ## Usage 22 | 23 | ```ruby 24 | require "net/http" 25 | 26 | proxy_arguments = [proxy_server,] # ... 27 | http = Net::HTTP.new(hostname, nil, *proxy_arguments) 28 | http.instance_variable_set(:@sspi_enabled, true) 29 | 30 | req = Net::HTTP::Get.new('/todos/1') 31 | http.request(req) 32 | ``` 33 | 34 | See [Proxy Server in Net::HTTP] for proxy arguments. 35 | 36 | [Proxy Server in Net::HTTP]: https://docs.ruby-lang.org/en/master/Net/HTTP.html#class-Net::HTTP-label-Proxy+Server 37 | 38 | ## Development 39 | 40 | To install this gem onto your local machine, run `bundle exec rake 41 | install`. To release a new version, update the version number in 42 | `version.rb`, and then run `bundle exec rake release`, which will 43 | create a git tag for the version, push git commits and the created 44 | tag, and push the `.gem` file to [rubygems.org](https://rubygems.org). 45 | 46 | ## Contributing 47 | 48 | Bug reports and pull requests are welcome on [GitHub]. 49 | 50 | [GitHub]: https://github.com/ruby/net-http-sspi 51 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Ruby 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: '35 6 * * *' 8 | workflow_dispatch: 9 | 10 | jobs: 11 | ruby-versions: 12 | if: ${{ startsWith(github.repository, 'ruby/') || github.event_name != 'schedule' }} 13 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master 14 | with: 15 | engine: cruby 16 | min_version: 3.3 17 | 18 | build: 19 | needs: ruby-versions 20 | name: CI (${{ matrix.ruby }} / ${{ matrix.os }}) 21 | runs-on: ${{ matrix.os }} 22 | outputs: 23 | chksum_ubuntu: ${{ steps.build.outputs.chksum_ubuntu }} 24 | chksum_windows: ${{ steps.build.outputs.chksum_windows }} 25 | strategy: 26 | matrix: 27 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} 28 | os: [ ubuntu-24.04, windows-latest ] 29 | 30 | steps: 31 | - name: git config 32 | run: | 33 | git config --global core.autocrlf false 34 | git config --global core.eol lf 35 | git config --global advice.detachedHead 0 36 | - uses: actions/checkout@v4 37 | - name: Set up Ruby 38 | uses: ruby/setup-ruby@v1 39 | with: 40 | ruby-version: ${{ matrix.ruby }} 41 | bundler-cache: true 42 | - name: Run the default task 43 | run: bundle exec rake 44 | 45 | - id: build 46 | run: | 47 | bundle exec rake build 48 | sum="`sha256sum -b pkg/*`" 49 | echo "$sum" 50 | echo "chksum_${BUILD_PLATFORM%%-*}=$sum" >> $GITHUB_OUTPUT 51 | echo "pkg=${GITHUB_REPOSITORY#*/}" >> $GITHUB_OUTPUT 52 | if: >- 53 | ${{ 54 | github.event_name == 'push' && 55 | (matrix.ruby == needs.ruby-versions.outputs.latest) 56 | }} 57 | env: 58 | BUILD_PLATFORM: ${{ matrix.os }} 59 | shell: bash 60 | - name: Upload package 61 | uses: actions/upload-artifact@v4 62 | with: 63 | path: pkg/*.gem 64 | name: ${{steps.build.outputs.pkg}}-${{matrix.os}} 65 | if: >- 66 | steps.build.outputs.pkg && 67 | startsWith(matrix.os, 'ubuntu') 68 | 69 | comp: 70 | name: ${{ github.workflow }} result 71 | runs-on: ubuntu-latest 72 | needs: [build] 73 | steps: 74 | - run: test "$chksum_ubuntu" = "$chksum_windows" 75 | working-directory: 76 | shell: bash 77 | env: 78 | chksum_ubuntu: ${{ needs.build.outputs.chksum_ubuntu }} 79 | chksum_windows: ${{ needs.build.outputs.chksum_windows }} 80 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Ruby is copyrighted free software by Yukihiro Matsumoto . 2 | You can redistribute it and/or modify it under either the terms of the 3 | 2-clause BSDL (see the file BSDL), or the conditions below: 4 | 5 | 1. You may make and give away verbatim copies of the source form of the 6 | software without restriction, provided that you duplicate all of the 7 | original copyright notices and associated disclaimers. 8 | 9 | 2. You may modify your copy of the software in any way, provided that 10 | you do at least ONE of the following: 11 | 12 | a. place your modifications in the Public Domain or otherwise 13 | make them Freely Available, such as by posting said 14 | modifications to Usenet or an equivalent medium, or by allowing 15 | the author to include your modifications in the software. 16 | 17 | b. use the modified software only within your corporation or 18 | organization. 19 | 20 | c. give non-standard binaries non-standard names, with 21 | instructions on where to get the original software distribution. 22 | 23 | d. make other distribution arrangements with the author. 24 | 25 | 3. You may distribute the software in object code or binary form, 26 | provided that you do at least ONE of the following: 27 | 28 | a. distribute the binaries and library files of the software, 29 | together with instructions (in the manual page or equivalent) 30 | on where to get the original distribution. 31 | 32 | b. accompany the distribution with the machine-readable source of 33 | the software. 34 | 35 | c. give non-standard binaries non-standard names, with 36 | instructions on where to get the original software distribution. 37 | 38 | d. make other distribution arrangements with the author. 39 | 40 | 4. You may modify and include the part of the software into any other 41 | software (possibly commercial). But some files in the distribution 42 | are not written by the author, so that they are not under these terms. 43 | 44 | For the list of those files and their copying conditions, see the 45 | file LEGAL. 46 | 47 | 5. The scripts and library files supplied as input to or produced as 48 | output from the software do not automatically fall under the 49 | copyright of the software, but belong to whomever generated them, 50 | and may be sold commercially, and may be aggregated with this 51 | software. 52 | 53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 56 | PURPOSE. 57 | -------------------------------------------------------------------------------- /lib/net/http/sspi.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # 3 | # = win32/sspi.rb 4 | # 5 | # Copyright (c) 2006-2007 Justin Bailey 6 | # 7 | # Written and maintained by Justin Bailey . 8 | # 9 | # This program is free software. You can re-distribute and/or 10 | # modify this program under the same terms of ruby itself --- 11 | # Ruby Distribution License or GNU General Public License. 12 | # 13 | 14 | require 'fiddle/import' 15 | 16 | # Implements bindings to Win32 SSPI functions, focused on authentication to a proxy server over HTTP. 17 | module Net 18 | module HTTP 19 | module SSPI 20 | # Specifies how credential structure requested will be used. Only SECPKG_CRED_OUTBOUND is used 21 | # here. 22 | SECPKG_CRED_INBOUND = 0x00000001 23 | SECPKG_CRED_OUTBOUND = 0x00000002 24 | SECPKG_CRED_BOTH = 0x00000003 25 | 26 | # Format of token. NETWORK format is used here. 27 | SECURITY_NATIVE_DREP = 0x00000010 28 | SECURITY_NETWORK_DREP = 0x00000000 29 | 30 | # InitializeSecurityContext Requirement flags 31 | ISC_REQ_REPLAY_DETECT = 0x00000004 32 | ISC_REQ_SEQUENCE_DETECT = 0x00000008 33 | ISC_REQ_CONFIDENTIALITY = 0x00000010 34 | ISC_REQ_USE_SESSION_KEY = 0x00000020 35 | ISC_REQ_PROMPT_FOR_CREDS = 0x00000040 36 | ISC_REQ_CONNECTION = 0x00000800 37 | 38 | # Win32 API Functions. Uses Win32API to bind methods to constants contained in class. 39 | module API 40 | extend Fiddle::Importer 41 | dlload "secur32.dll" 42 | [ 43 | # Can be called with AcquireCredentialsHandleA.call() 44 | "unsigned long AcquireCredentialsHandleA(void *, void *, unsigned long, void *, void *, void *, void *, void *, void *)", 45 | # Can be called with InitializeSecurityContextA.call() 46 | "unsigned long InitializeSecurityContextA(void *, void *, void *, unsigned long, unsigned long, unsigned long, void *, unsigned long, void *, void *, void *, void *)", 47 | # Can be called with DeleteSecurityContext.call() 48 | "unsigned long DeleteSecurityContext(void *)", 49 | # Can be called with FreeCredentialsHandle.call() 50 | "unsigned long FreeCredentialsHandle(void *)" 51 | ].each do |fn| 52 | cfunc = extern fn, :stdcall 53 | const_set cfunc.name.intern, cfunc 54 | end 55 | end 56 | 57 | # SecHandle struct 58 | class SecurityHandle 59 | def upper 60 | @struct.unpack1("x4L") 61 | end 62 | 63 | def lower 64 | @struct.unpack1("L") 65 | end 66 | 67 | def to_p 68 | @struct ||= "\0" * 8 69 | end 70 | end 71 | 72 | # Some familiar aliases for the SecHandle structure 73 | CredHandle = CtxtHandle = SecurityHandle 74 | 75 | # TimeStamp struct 76 | class TimeStamp 77 | attr_reader :struct 78 | 79 | def to_p 80 | @struct ||= "\0" * 8 81 | end 82 | end 83 | 84 | # Creates binary representations of a SecBufferDesc structure, 85 | # including the SecBuffer contained inside. 86 | class SecurityBuffer 87 | 88 | SECBUFFER_TOKEN = 2 # Security token 89 | 90 | TOKENBUFSIZE = 12288 91 | SECBUFFER_VERSION = 0 92 | 93 | def initialize(buffer = nil) 94 | @buffer = buffer || "\0" * TOKENBUFSIZE 95 | @bufferSize = @buffer.length 96 | @type = SECBUFFER_TOKEN 97 | end 98 | 99 | def bufferSize 100 | unpack 101 | @bufferSize 102 | end 103 | 104 | def bufferType 105 | unpack 106 | @type 107 | end 108 | 109 | def token 110 | unpack 111 | @buffer 112 | end 113 | 114 | def to_p 115 | # Assumption is that when to_p is called we are going to get a packed structure. Therefore, 116 | # set @unpacked back to nil so we know to unpack when accessors are next accessed. 117 | @unpacked = nil 118 | # Assignment of inner structure to variable is very important here. Without it, 119 | # will not be able to unpack changes to the structure. Alternative, nested unpacks, 120 | # does not work (i.e. @struct.unpack("LLP12")[2].unpack("LLP12") results in "no associated pointer") 121 | @sec_buffer ||= [@bufferSize, @type, @buffer].pack("LLP") 122 | @struct ||= [SECBUFFER_VERSION, 1, @sec_buffer].pack("LLP") 123 | end 124 | 125 | private 126 | 127 | # Unpacks the SecurityBufferDesc structure into member variables. We 128 | # only want to do this once per struct, so the struct is deleted 129 | # after unpacking. 130 | def unpack 131 | if ! @unpacked && @sec_buffer && @struct 132 | @bufferSize, @type = @sec_buffer.unpack("LL") 133 | @buffer = @sec_buffer.unpack1("x8P#{@bufferSize}") 134 | @struct = nil 135 | @sec_buffer = nil 136 | @unpacked = true 137 | end 138 | end 139 | end 140 | 141 | # SEC_WINNT_AUTH_IDENTITY structure 142 | class Identity 143 | SEC_WINNT_AUTH_IDENTITY_ANSI = 0x1 144 | 145 | attr_accessor :user, :domain, :password 146 | 147 | def initialize(user = nil, domain = nil, password = nil) 148 | @user = user 149 | @domain = domain 150 | @password = password 151 | @flags = SEC_WINNT_AUTH_IDENTITY_ANSI 152 | end 153 | 154 | def to_p 155 | [@user, @user ? @user.length : 0, 156 | @domain, @domain ? @domain.length : 0, 157 | @password, @password ? @password.length : 0, 158 | @flags].pack("PLPLPLL") 159 | end 160 | end 161 | 162 | # Takes a return result from an SSPI function and interprets the value. 163 | class SSPIResult 164 | # Good results 165 | SEC_E_OK = 0x00000000 166 | SEC_I_CONTINUE_NEEDED = 0x00090312 167 | 168 | # These are generally returned by InitializeSecurityContext 169 | SEC_E_INSUFFICIENT_MEMORY = 0x80090300 170 | SEC_E_INTERNAL_ERROR = 0x80090304 171 | SEC_E_INVALID_HANDLE = 0x80090301 172 | SEC_E_INVALID_TOKEN = 0x80090308 173 | SEC_E_LOGON_DENIED = 0x8009030C 174 | SEC_E_NO_AUTHENTICATING_AUTHORITY = 0x80090311 175 | SEC_E_NO_CREDENTIALS = 0x8009030E 176 | SEC_E_TARGET_UNKNOWN = 0x80090303 177 | SEC_E_UNSUPPORTED_FUNCTION = 0x80090302 178 | SEC_E_WRONG_PRINCIPAL = 0x80090322 179 | 180 | # These are generally returned by AcquireCredentialsHandle 181 | SEC_E_NOT_OWNER = 0x80090306 182 | SEC_E_SECPKG_NOT_FOUND = 0x80090305 183 | SEC_E_UNKNOWN_CREDENTIALS = 0x8009030D 184 | 185 | RESULT_MAP = constants.to_h {|v| [const_get(v), v]}.freeze 186 | 187 | attr_reader :value 188 | 189 | def initialize(value) 190 | # convert to unsigned long 191 | value &= 0xffffffff 192 | raise "#{value.to_s(16)} is not a recognized result" unless RESULT_MAP.key? value 193 | @value = value 194 | end 195 | 196 | def to_s 197 | RESULT_MAP[@value].to_s 198 | end 199 | 200 | def ok? 201 | @value == SEC_I_CONTINUE_NEEDED || @value == SEC_E_OK 202 | end 203 | 204 | def ==(other) 205 | case other 206 | when SSPIResult 207 | @value == other.value 208 | when Integer 209 | @value == other 210 | when Symbol 211 | RESULT_MAP[@value] == other 212 | else 213 | false 214 | end 215 | end 216 | end 217 | 218 | # Handles "Negotiate" type authentication. Geared towards authenticating with a proxy server over HTTP 219 | class NegotiateAuth 220 | attr_accessor :credentials, :context, :contextAttributes, :user, :domain 221 | 222 | # Default request flags for SSPI functions 223 | REQUEST_FLAGS = ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONNECTION 224 | 225 | # NTLM tokens start with this header always. Encoding alone adds "==" and newline, so remove those 226 | B64_TOKEN_PREFIX = ["NTLMSSP"].pack("m").delete("=\n") 227 | 228 | # Given a connection and a request path, performs authentication as the current user and returns 229 | # the response from a GET request. The connection should be a Net::HTTP object, and it should 230 | # have been constructed using the Net::HTTP.Proxy method, but anything that responds to "get" will work. 231 | # If a user and domain are given, will authenticate as the given user. 232 | # Returns the response received from the get method (usually Net::HTTPResponse) 233 | def NegotiateAuth.proxy_auth_get(http, path, user = nil, domain = nil) 234 | raise "http must respond to :get" unless http.respond_to?(:get) 235 | nego_auth = self.new user, domain 236 | 237 | resp = http.get path, { "Proxy-Authorization" => "Negotiate " + nego_auth.get_initial_token } 238 | if resp["Proxy-Authenticate"] 239 | resp = http.get path, { "Proxy-Authorization" => "Negotiate " + nego_auth.complete_authentication(resp["Proxy-Authenticate"].split(" ").last.strip) } 240 | end 241 | 242 | resp 243 | end 244 | 245 | # Creates a new instance ready for authentication as the given user in the given domain. 246 | # Defaults to current user and domain as defined by ENV["USERDOMAIN"] and ENV["USERNAME"] if 247 | # no arguments are supplied. 248 | def initialize(user = nil, domain = nil) 249 | if user.nil? && domain.nil? && ENV["USERNAME"].nil? && ENV["USERDOMAIN"].nil? 250 | raise "A username or domain must be supplied since they cannot be retrieved from the environment" 251 | end 252 | 253 | @user = user || ENV["USERNAME"] 254 | @domain = domain || ENV["USERDOMAIN"] 255 | end 256 | 257 | # Gets the initial Negotiate token. Returns it as a base64 encoded string suitable for use in HTTP. Can 258 | # be easily decoded, however. 259 | def get_initial_token 260 | raise "This object is no longer usable because its resources have been freed." if @cleaned_up 261 | get_credentials 262 | 263 | outputBuffer = SecurityBuffer.new 264 | @context = CtxtHandle.new 265 | @contextAttributes = "\0" * 4 266 | 267 | result = SSPIResult.new(API::InitializeSecurityContextA.call(@credentials.to_p, nil, nil, 268 | REQUEST_FLAGS,0, SECURITY_NETWORK_DREP, nil, 0, @context.to_p, outputBuffer.to_p, @contextAttributes, TimeStamp.new.to_p)) 269 | 270 | if result.ok? then 271 | return encode_token(outputBuffer.token) 272 | else 273 | raise "Error: #{result.to_s}" 274 | end 275 | end 276 | 277 | # Takes a token and gets the next token in the Negotiate authentication chain. Token can be Base64 encoded or not. 278 | # The token can include the "Negotiate" header and it will be stripped. 279 | # Does not indicate if SEC_I_CONTINUE or SEC_E_OK was returned. 280 | # Token returned is Base64 encoded w/ all new lines removed. 281 | def complete_authentication(token) 282 | raise "This object is no longer usable because its resources have been freed." if @cleaned_up 283 | 284 | # Nil token OK, just set it to empty string 285 | token = "" if token.nil? 286 | 287 | if token.start_with? "Negotiate" 288 | # If the Negotiate prefix is passed in, assume we are seeing "Negotiate " and get the token. 289 | token = token.split(" ", 2).last 290 | end 291 | 292 | if token.start_with? B64_TOKEN_PREFIX 293 | # indicates base64 encoded token 294 | token = token.strip.unpack1("m") 295 | end 296 | 297 | outputBuffer = SecurityBuffer.new 298 | result = SSPIResult.new(API::InitializeSecurityContextA.call(@credentials.to_p, @context.to_p, nil, 299 | REQUEST_FLAGS, 0, SECURITY_NETWORK_DREP, SecurityBuffer.new(token).to_p, 0, 300 | @context.to_p, 301 | outputBuffer.to_p, @contextAttributes, TimeStamp.new.to_p)) 302 | 303 | if result.ok? then 304 | return encode_token(outputBuffer.token) 305 | else 306 | raise "Error: #{result.to_s}" 307 | end 308 | ensure 309 | # need to make sure we don't clean up if we've already cleaned up. 310 | clean_up unless @cleaned_up 311 | end 312 | 313 | private 314 | 315 | def clean_up 316 | # free structures allocated 317 | @cleaned_up = true 318 | API::FreeCredentialsHandle.call(@credentials.to_p) 319 | API::DeleteSecurityContext.call(@context.to_p) 320 | @context = nil 321 | @credentials = nil 322 | @contextAttributes = nil 323 | end 324 | 325 | # Gets credentials based on user, domain or both. If both are nil, an error occurs 326 | def get_credentials 327 | @credentials = CredHandle.new 328 | ts = TimeStamp.new 329 | @identity = Identity.new @user, @domain 330 | result = SSPIResult.new(API::AcquireCredentialsHandleA.call(nil, "Negotiate", SECPKG_CRED_OUTBOUND, nil, @identity.to_p, 331 | nil, nil, @credentials.to_p, ts.to_p)) 332 | raise "Error acquire credentials: #{result}" unless result.ok? 333 | end 334 | 335 | def encode_token(t) 336 | [t].pack("m0") 337 | end 338 | end 339 | end 340 | end 341 | end 342 | --------------------------------------------------------------------------------