Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Fix using pageant on windows in ruby 19 #34

Merged
merged 6 commits into from

3 participants

@rliebling

I merged the patch from #14 (by taking master on graffic/net-ssh), resolved the merge conflicts, and fixed a few remaining test issues. On my windows box, all tests pass both in 1.8.7 and 1.9.3

Note: the :forward_agent option still does not work with pageant.

graffic and others added some commits
@graffic graffic test_ket_manager: Tests should pass in windows too. 035cf6b
@graffic graffic All tests pass Ruby 1.9.2 on Windows a87f437
@graffic graffic Added the same description as github has. ef23039
@graffic graffic Adds support for Putty ssh agent and ruby 1.9 (Windows) a5ebe60
rich Merge remote-tracking branch 'graffic/master'
and fix some tests for windows

* graffic/master:
  Adds support for Putty ssh agent and ruby 1.9 (Windows)
  Added the same description as github has.
  All tests pass Ruby 1.9.2 on Windows
  test_ket_manager: Tests should pass in windows too.

Conflicts:
	test/authentication/test_key_manager.rb
	test/transport/test_algorithms.rb
6c878c8
rich pageant.rb - whitespace (tabs to spaces) 28e5396
@delano
Collaborator

Looks like a good patch. This is a long awaited fix so greatly appreciated!

I do not have a Windows machine to verify it on. I'll pull it into master but I'll need someone to verify it before I create a release. I'll reply on the previous thread and get in touch with a few folks.

@delano delano merged commit a547758 into from
@rliebling
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 28, 2011
  1. @graffic
  2. @graffic
Commits on Mar 1, 2011
  1. @graffic
  2. @graffic
Commits on Jan 22, 2012
  1. Merge remote-tracking branch 'graffic/master'

    rich authored
    and fix some tests for windows
    
    * graffic/master:
      Adds support for Putty ssh agent and ruby 1.9 (Windows)
      Added the same description as github has.
      All tests pass Ruby 1.9.2 on Windows
      test_ket_manager: Tests should pass in windows too.
    
    Conflicts:
    	test/authentication/test_key_manager.rb
    	test/transport/test_algorithms.rb
  2. pageant.rb - whitespace (tabs to spaces)

    rich authored
This page is out of date. Refresh to see the latest.
View
6 lib/net/ssh/authentication/agent.rb
@@ -3,8 +3,8 @@
require 'net/ssh/loggable'
require 'net/ssh/transport/server_version'
-# Only load pageant on Windows, Ruby 1.8.x
-if File::ALT_SEPARATOR && !(RUBY_PLATFORM =~ /java/) && RUBY_VERSION < "1.9"
+# Only load pageant on Windows
+if File::ALT_SEPARATOR && !(RUBY_PLATFORM =~ /java/)
require 'net/ssh/authentication/pageant'
end
@@ -135,7 +135,7 @@ def sign(key, data)
# Returns the agent socket factory to use.
def agent_socket_factory
if File::ALT_SEPARATOR
- Pageant::Socket
+ Pageant::socket_factory
else
UNIXSocket
end
View
107 lib/net/ssh/authentication/pageant.rb
@@ -1,5 +1,13 @@
require 'dl/import'
-require 'dl/struct'
+
+if RUBY_VERSION < "1.9"
+ require 'dl/struct'
+end
+
+if RUBY_VERSION =~ /^1.9/
+ require 'dl/types'
+ require 'dl'
+end
require 'net/ssh/errors'
@@ -17,15 +25,23 @@ module Pageant
# From Putty pageant.c
AGENT_MAX_MSGLEN = 8192
AGENT_COPYDATA_ID = 0x804e50ba
-
+
# The definition of the Windows methods and data structures used in
# communicating with the pageant process.
module Win
- extend DL::Importable
-
- dlload 'user32'
- dlload 'kernel32'
-
+ if RUBY_VERSION < "1.9"
+ extend DL::Importable
+
+ dlload 'user32'
+ dlload 'kernel32'
+ end
+
+ if RUBY_VERSION =~ /^1.9/
+ extend DL::Importer
+ dlload 'user32','kernel32'
+ include DL::Win32Types
+ end
+
typealias("LPCTSTR", "char *") # From winnt.h
typealias("LPVOID", "void *") # From winnt.h
typealias("LPCVOID", "const void *") # From windef.h
@@ -52,7 +68,7 @@ module Win
# args: hFile, (ignored), flProtect, dwMaximumSizeHigh,
# dwMaximumSizeLow, lpName
extern 'HANDLE CreateFileMapping(HANDLE, void *, DWORD, DWORD, ' +
- 'DWORD, LPCTSTR)'
+ 'DWORD, LPCTSTR)'
# args: hFileMappingObject, dwDesiredAccess, dwFileOffsetHigh,
# dwfileOffsetLow, dwNumberOfBytesToMap
@@ -66,7 +82,11 @@ module Win
# args: hWnd, Msg, wParam, lParam, fuFlags, uTimeout, lpdwResult
extern 'LRESULT SendMessageTimeout(HWND, UINT, WPARAM, LPARAM, ' +
- 'UINT, UINT, PDWORD_PTR)'
+ 'UINT, UINT, PDWORD_PTR)'
+ if RUBY_VERSION < "1.9"
+ alias_method :FindWindow,:findWindow
+ module_function :FindWindow
+ end
end
# This is the pseudo-socket implementation that mimics the interface of
@@ -87,7 +107,7 @@ def self.open(location=nil)
# Create a new instance that communicates with the running pageant
# instance. If no such instance is running, this will cause an error.
def initialize
- @win = Win.findWindow("Pageant", "Pageant")
+ @win = Win.FindWindow("Pageant", "Pageant")
if @win == 0
raise Net::SSH::Exception,
@@ -97,7 +117,7 @@ def initialize
@res = nil
@pos = 0
end
-
+
# Forwards the data to #send_query, ignoring any arguments after
# the first. Returns 0.
def send(data, *args)
@@ -132,11 +152,11 @@ def send_query(query)
end
ptr[0] = query
-
+
cds = [AGENT_COPYDATA_ID, mapname.size + 1, mapname].
pack("LLp").to_ptr
succ = Win.sendMessageTimeout(@win, Win::WM_COPYDATA, Win::NULL,
- cds, Win::SMTO_NORMAL, 5000, id)
+ cds, Win::SMTO_NORMAL, 5000, id)
if succ > 0
retlen = 4 + ptr.to_s(4).unpack("N")[0]
@@ -178,6 +198,67 @@ def read(n = nil)
end
+ # Socket changes for Ruby 1.9
+ # Functionality is the same as Ruby 1.8 but it includes the new calls to
+ # the DL module as well as other pointer transformations
+ class Socket19 < Socket
+ # Packages the given query string and sends it to the pageant
+ # process via the Windows messaging subsystem. The result is
+ # cached, to be returned piece-wise when #read is called.
+ def send_query(query)
+ res = nil
+ filemap = 0
+ ptr = nil
+ id = DL.malloc(DL::SIZEOF_LONG)
+
+ mapname = "PageantRequest%08x\000" % Win.GetCurrentThreadId()
+
+ filemap = Win.CreateFileMapping(Win::INVALID_HANDLE_VALUE,
+ Win::NULL,
+ Win::PAGE_READWRITE, 0,
+ AGENT_MAX_MSGLEN, mapname)
+
+ if filemap == 0 || filemap == Win::INVALID_HANDLE_VALUE
+ raise Net::SSH::Exception,
+ "Creation of file mapping failed"
+ end
+
+ ptr = Win.MapViewOfFile(filemap, Win::FILE_MAP_WRITE, 0, 0,
+ 0)
+
+ if ptr.nil? || ptr.null?
+ raise Net::SSH::Exception, "Mapping of file failed"
+ end
+
+ DL::CPtr.new(ptr)[0,query.size]=query
+
+ cds = DL::CPtr.to_ptr [AGENT_COPYDATA_ID, mapname.size + 1, mapname].
+ pack("LLp")
+ succ = Win.SendMessageTimeout(@win, Win::WM_COPYDATA, Win::NULL,
+ cds, Win::SMTO_NORMAL, 5000, id)
+
+ if succ > 0
+ retlen = 4 + ptr.to_s(4).unpack("N")[0]
+ res = ptr.to_s(retlen)
+ end
+
+ return res
+ ensure
+ Win.UnmapViewOfFile(ptr) unless ptr.nil? || ptr.null?
+ Win.CloseHandle(filemap) if filemap != 0
+ end
+ end
+
+ # Selects which socket to use depending on the ruby version
+ # This is needed due changes in the DL module.
+ def self.socket_factory
+ if RUBY_VERSION < "1.9"
+ Socket
+ else
+ Socket19
+ end
+ end
+
end
end; end; end
View
2  net-ssh.gemspec
@@ -3,7 +3,7 @@
s.rubyforge_project = 'net-ssh'
s.version = "2.3.0"
s.summary = "Net::SSH: a pure-Ruby implementation of the SSH2 client protocol."
- s.description = s.summary
+ s.description = s.summary + " It allows you to write programs that invoke and interact with processes on remote servers, via SSH2."
s.authors = ["Jamis Buck", "Delano Mandelbaum"]
s.email = ["net-ssh@solutious.com"]
s.homepage = "http://github.com/net-ssh/net-ssh"
View
29 test/authentication/test_key_manager.rb
@@ -18,7 +18,9 @@ def test_add_ensures_list_is_unique
manager.add "/second"
manager.add "/third"
manager.add "/second"
- assert_equal %w(/first /second /third), manager.key_files
+ assert_true manager.key_files.length == 3
+ final_files = manager.key_files.map {|item| item.split('/').last}
+ assert_equal %w(first second third), final_files
end
def test_use_agent_should_be_set_to_false_if_agent_could_not_be_found
@@ -30,9 +32,10 @@ def test_use_agent_should_be_set_to_false_if_agent_could_not_be_found
def test_each_identity_should_load_from_key_files
manager.stubs(:agent).returns(nil)
-
- stub_file_private_key "/first", rsa
- stub_file_private_key "/second", dsa
+ first = File.expand_path("/first")
+ second = File.expand_path("/second")
+ stub_file_private_key first, rsa
+ stub_file_private_key second, dsa
identities = []
manager.each_identity { |identity| identities << identity }
@@ -40,9 +43,9 @@ def test_each_identity_should_load_from_key_files
assert_equal 2, identities.length
assert_equal rsa.to_blob, identities.first.to_blob
assert_equal dsa.to_blob, identities.last.to_blob
-
- assert_equal({:from => :file, :file => "/first", :key => rsa}, manager.known_identities[rsa])
- assert_equal({:from => :file, :file => "/second", :key => dsa}, manager.known_identities[dsa])
+
+ assert_equal({:from => :file, :file => first, :key => rsa}, manager.known_identities[rsa])
+ assert_equal({:from => :file, :file => second, :key => dsa}, manager.known_identities[dsa])
end
def test_identities_should_load_from_agent
@@ -62,7 +65,8 @@ def test_identities_should_load_from_agent
def test_only_identities_with_key_files_should_load_from_agent_of_keys_only_set
manager(:keys_only => true).stubs(:agent).returns(agent)
- stub_file_private_key "/first", rsa
+ first = File.expand_path("/first")
+ stub_file_private_key first, rsa
identities = []
manager.each_identity { |identity| identities << identity }
@@ -76,8 +80,10 @@ def test_only_identities_with_key_files_should_load_from_agent_of_keys_only_set
def test_identities_without_public_key_files_should_not_be_touched_if_identity_loaded_from_agent
manager.stubs(:agent).returns(agent)
- stub_file_public_key "/first", rsa
- stub_file_private_key "/second", dsa, :passphrase => :should_not_be_asked
+ first = File.expand_path("/first")
+ stub_file_public_key first, rsa
+ second = File.expand_path("/second")
+ stub_file_private_key second, dsa, :passphrase => :should_not_be_asked
identities = []
manager.each_identity do |identity|
@@ -98,7 +104,8 @@ def test_sign_with_agent_originated_key_should_request_signature_from_agent
def test_sign_with_file_originated_key_should_load_private_key_and_sign_with_it
manager.stubs(:agent).returns(nil)
- stub_file_private_key "/first", rsa(512)
+ first = File.expand_path("/first")
+ stub_file_private_key first, rsa(512)
rsa.expects(:ssh_do_sign).with("hello, world").returns("abcxyz123")
manager.each_identity { |identity| } # preload the known_identities
assert_equal "\0\0\0\assh-rsa\0\0\0\011abcxyz123", manager.sign(rsa, "hello, world")
View
5 test/test_config.rb
@@ -3,8 +3,9 @@
class TestConfig < Test::Unit::TestCase
def test_load_for_non_existant_file_should_return_empty_hash
- File.expects(:readable?).with("/bogus/file").returns(false)
- assert_equal({}, Net::SSH::Config.load("/bogus/file", "host.name"))
+ bogus_file = File.expand_path("/bogus/file")
+ File.expects(:readable?).with(bogus_file).returns(false)
+ assert_equal({}, Net::SSH::Config.load(bogus_file, "host.name"))
end
def test_load_should_expand_path
View
46 test/test_key_factory.rb
@@ -2,53 +2,57 @@
require 'net/ssh/key_factory'
class TestKeyFactory < Test::Unit::TestCase
+ def setup
+ @key_file = File.expand_path("/key-file")
+ end
+
def test_load_unencrypted_private_RSA_key_should_return_key
- File.expects(:read).with("/key-file").returns(rsa_key.export)
- assert_equal rsa_key.to_der, Net::SSH::KeyFactory.load_private_key("/key-file").to_der
+ File.expects(:read).with(@key_file).returns(rsa_key.export)
+ assert_equal rsa_key.to_der, Net::SSH::KeyFactory.load_private_key(@key_file).to_der
end
def test_load_unencrypted_private_DSA_key_should_return_key
- File.expects(:read).with("/key-file").returns(dsa_key.export)
- assert_equal dsa_key.to_der, Net::SSH::KeyFactory.load_private_key("/key-file").to_der
+ File.expects(:read).with(@key_file).returns(dsa_key.export)
+ assert_equal dsa_key.to_der, Net::SSH::KeyFactory.load_private_key(@key_file).to_der
end
def test_load_encrypted_private_RSA_key_should_prompt_for_password_and_return_key
- File.expects(:read).with("/key-file").returns(encrypted(rsa_key, "password"))
- Net::SSH::KeyFactory.expects(:prompt).with("Enter passphrase for /key-file:", false).returns("password")
- assert_equal rsa_key.to_der, Net::SSH::KeyFactory.load_private_key("/key-file").to_der
+ File.expects(:read).with(@key_file).returns(encrypted(rsa_key, "password"))
+ Net::SSH::KeyFactory.expects(:prompt).with("Enter passphrase for #{@key_file}:", false).returns("password")
+ assert_equal rsa_key.to_der, Net::SSH::KeyFactory.load_private_key(@key_file).to_der
end
def test_load_encrypted_private_RSA_key_with_password_should_not_prompt_and_return_key
- File.expects(:read).with("/key-file").returns(encrypted(rsa_key, "password"))
- assert_equal rsa_key.to_der, Net::SSH::KeyFactory.load_private_key("/key-file", "password").to_der
+ File.expects(:read).with(@key_file).returns(encrypted(rsa_key, "password"))
+ assert_equal rsa_key.to_der, Net::SSH::KeyFactory.load_private_key(@key_file, "password").to_der
end
def test_load_encrypted_private_DSA_key_should_prompt_for_password_and_return_key
- File.expects(:read).with("/key-file").returns(encrypted(dsa_key, "password"))
- Net::SSH::KeyFactory.expects(:prompt).with("Enter passphrase for /key-file:", false).returns("password")
- assert_equal dsa_key.to_der, Net::SSH::KeyFactory.load_private_key("/key-file").to_der
+ File.expects(:read).with(@key_file).returns(encrypted(dsa_key, "password"))
+ Net::SSH::KeyFactory.expects(:prompt).with("Enter passphrase for #{@key_file}:", false).returns("password")
+ assert_equal dsa_key.to_der, Net::SSH::KeyFactory.load_private_key(@key_file).to_der
end
def test_load_encrypted_private_DSA_key_with_password_should_not_prompt_and_return_key
- File.expects(:read).with("/key-file").returns(encrypted(dsa_key, "password"))
- assert_equal dsa_key.to_der, Net::SSH::KeyFactory.load_private_key("/key-file", "password").to_der
+ File.expects(:read).with(@key_file).returns(encrypted(dsa_key, "password"))
+ assert_equal dsa_key.to_der, Net::SSH::KeyFactory.load_private_key(@key_file, "password").to_der
end
def test_load_encrypted_private_key_should_give_three_tries_for_the_password_and_then_raise_exception
- File.expects(:read).with("/key-file").returns(encrypted(rsa_key, "password"))
- Net::SSH::KeyFactory.expects(:prompt).times(3).with("Enter passphrase for /key-file:", false).returns("passwod","passphrase","passwd")
- assert_raises(OpenSSL::PKey::RSAError) { Net::SSH::KeyFactory.load_private_key("/key-file") }
+ File.expects(:read).with(@key_file).returns(encrypted(rsa_key, "password"))
+ Net::SSH::KeyFactory.expects(:prompt).times(3).with("Enter passphrase for #{@key_file}:", false).returns("passwod","passphrase","passwd")
+ assert_raises(OpenSSL::PKey::RSAError) { Net::SSH::KeyFactory.load_private_key(@key_file) }
end
def test_load_encrypted_private_key_should_raise_exception_without_asking_passphrase
- File.expects(:read).with("/key-file").returns(encrypted(rsa_key, "password"))
+ File.expects(:read).with(@key_file).returns(encrypted(rsa_key, "password"))
Net::SSH::KeyFactory.expects(:prompt).never
- assert_raises(OpenSSL::PKey::RSAError) { Net::SSH::KeyFactory.load_private_key("/key-file", nil, false) }
+ assert_raises(OpenSSL::PKey::RSAError) { Net::SSH::KeyFactory.load_private_key(@key_file, nil, false) }
end
def test_load_public_rsa_key_should_return_key
- File.expects(:read).with("/key-file").returns(public(rsa_key))
- assert_equal rsa_key.to_blob, Net::SSH::KeyFactory.load_public_key("/key-file").to_blob
+ File.expects(:read).with(@key_file).returns(public(rsa_key))
+ assert_equal rsa_key.to_blob, Net::SSH::KeyFactory.load_public_key(@key_file).to_blob
end
private
Something went wrong with that request. Please try again.