--- net-ssh-1.0.10/doc/manual/manual.yml 2006-04-15 03:52:17.000000000 +0100 +++ net-ssh-1.0.10-agentforward/doc/manual/manual.yml 2007-03-24 12:28:47.318704257 +0000 @@ -21,8 +21,7 @@ - API Documentation: http://net-ssh.rubyforge.org/api recent_updates: - - "Removed documentation indicating that ruby-password will be used if present (it won't)" - - "Removed links to the wiki (which wasn't being used) and the faq (which never existed)" + - "Added information on ssh-agent forwarding." chapters: diff -urN --exclude '*~' net-ssh-1.0.10/doc/manual/parts/0007.txt net-ssh-1.0.10-agentforward/doc/manual/parts/0007.txt --- net-ssh-1.0.10/doc/manual/parts/0007.txt 2006-04-15 03:52:17.000000000 +0100 +++ net-ssh-1.0.10-agentforward/doc/manual/parts/0007.txt 2007-03-24 12:27:59.371231928 +0000 @@ -63,3 +63,5 @@ On Windows, the pageant process will be detected automatically, if it is running. A future version of Net::SSH may include it's own agent implementation as well, to make using an agent on a variety of platforms simpler. + +Agent forwarding is available, and may be requested with the :forward_agent option to Net::SSH.start. Agents are not forwarded by default. diff -urN --exclude '*~' net-ssh-1.0.10/doc/manual/parts/0008.txt net-ssh-1.0.10-agentforward/doc/manual/parts/0008.txt --- net-ssh-1.0.10/doc/manual/parts/0008.txt 2006-04-15 03:52:17.000000000 +0100 +++ net-ssh-1.0.10-agentforward/doc/manual/parts/0008.txt 2007-03-24 12:28:45.620275139 +0000 @@ -12,6 +12,7 @@ |^=. @:container@ | This is the dependency injection container to use when registering all of the services that Net::SSH uses internally. If unspecified (the default) a new container will be created. This option allows you to reuse a single container for multiple application components.| |^=. @:crypto_backend@ | This is the cryptography backend to use. It defaults to @:ossl@, which specifies the OpenSSL cryptography engine. Currently, this is the only supported backend, but in the future others may be provided, and this is how they would be selected.| |^=. @:encryption@ | This is the cipher algorithm to use when sending/receiving data to/from the remote server. It defaults to @3des-cbc@. Other valid algorithms supported by Net::SSH are @aes128-cbc@, @blowfish-cbc@, @aes256-cbc@, @aes192-cbc@, @idea-cbc@, and @none@. Note that the values you specify here are only _suggestions_, and if the server you are contacting cannot use your recommended algorithm, a fallback algorithm will be used (typically chosen in the order the algorithms were listed, above). This option may take an array, if you want to specify the order of the fallback algorithms to try, as well. | +|^=. @:forward_agent@ | Set to a true value to request that the local authentication agent be forwarded to the remote host. By default the agent will not be forwarded. | |^=. @:hmac@ | This specifies the "message authentication code" (MAC) algorithm to use to ensure that each packet transmitted and recieved is authentic. This defaults to @hmac-md5@. Other valid algorithms supported by Net::SSH are @hmac-sha1@, @hmac-md5-96@, @hmac-md5-sha1@, and @none@. Note that the values you specify here are only _suggestions_, and if the server you are contacting cannot use your recommended algorithm, a fallback algorithm will be used (typically chosen in the order the algorithms were listed, above). This option may take an array, if you want to specify the order of the fallback algorithms to try, as well. | |^=. @:host_key@ | This specifies the host key type that should be used when negotiating keys with the server. This defaults to @ssh-dss@, but may also be @ssh-rsa@. As with some other option types, the value you specify is only a recommendation, not a commandment, and if the server cannot honor the key type you specified, a fallback will be chosen from among the other supported types. If you wish to specify the fallback algorithms to try, you may pass an array as the value of this option, which contains (in order) the key types to try. | |^=. @:host_keys@ | This is an array of file names that contain the private keys which identify the host your script is running on. These default to @/etc/ssh/ssh_host_dsa_key@ and @/etc/ssh/ssh_host_rsa_key@ (which are both typically only readable by root). These keys are only used in hostbased authentication.| diff -urN --exclude '*~' net-ssh-1.0.10/examples/auth-forward.rb net-ssh-1.0.10-agentforward/examples/auth-forward.rb --- net-ssh-1.0.10/examples/auth-forward.rb 1970-01-01 01:00:00.000000000 +0100 +++ net-ssh-1.0.10-agentforward/examples/auth-forward.rb 2007-03-24 12:36:20.854710640 +0000 @@ -0,0 +1,41 @@ +require 'rubygems' + +$:.unshift "../lib" +require 'net/ssh' + + + +Net::SSH.start( 'localhost', { :verbose => :debug, :forward_agent => true } ) do |session| +#Net::SSH.start( 'localhost' ) do |session| + + def exec( command ) + lambda do |channel| + channel.exec command + channel.on_data do |ch,data| + ch[:data] ||= "" + ch[:data] << data + end + channel.on_extended_data do |ch,type,data| + ch[:extended_data] ||= [] + ch[:extended_data][type] ||= "" + ch[:extended_data][type] << data + end + end + end + + c = session.open_channel( &exec( "ssh -A munkyii.nodnol.org ssh-add -l" ) ) + + session.loop + + puts "----------------------------------" + if c.valid? + puts c[:data] + if c[:extended_data] && c[:extended_data][1] + puts "-- stderr: --" + puts c[:extended_data][1] + end + else + puts "channel was not opened: #{c.reason} (#{c.reason_code})" + end + +end diff -urN --exclude '*~' net-ssh-1.0.10/lib/net/ssh/connection/driver.rb net-ssh-1.0.10-agentforward/lib/net/ssh/connection/driver.rb --- net-ssh-1.0.10/lib/net/ssh/connection/driver.rb 2006-09-07 17:48:12.000000000 +0100 +++ net-ssh-1.0.10-agentforward/lib/net/ssh/connection/driver.rb 2007-03-22 19:59:06.181308270 +0000 @@ -134,7 +134,10 @@ # +true+. If no block is given, then the loop continues until there # are no more open channels on this connection. def loop( &block ) - block ||= proc { not @channel_map.empty? } + block ||= proc do + channels = @channel_map.reject {|k,v| v.type == 'auth-agent@openssh.com' } + not channels.empty? + end process while block.call end @@ -178,6 +181,17 @@ self end + # Send a channel request packet to the server. + def channel_request( type ) + writer = @buffers.writer + writer.write_byte CHANNEL_REQUEST + writer.write_long 0 # channel id + writer.write_string type + writer.write_byte 0 # want_confirm + + @session.send_message writer + end + # A convenience method for sending messages. def send_message( msg ) @session.send_message msg diff -urN --exclude '*~' net-ssh-1.0.10/lib/net/ssh/service/agentforward/driver.rb net-ssh-1.0.10-agentforward/lib/net/ssh/service/agentforward/driver.rb --- net-ssh-1.0.10/lib/net/ssh/service/agentforward/driver.rb 1970-01-01 01:00:00.000000000 +0100 +++ net-ssh-1.0.10-agentforward/lib/net/ssh/service/agentforward/driver.rb 2007-03-24 12:34:58.740916858 +0000 @@ -0,0 +1,78 @@ +#-- +# ============================================================================= +# Copyright (c) 2007, Chris Andrews (chris@nodnol.org), +# Jamis Buck (jgb3@email.byu.edu) +# All rights reserved. +# +# This source file is distributed as part of the Net::SSH Secure Shell Client +# library for Ruby. This file (and the library as a whole) may be used only as +# allowed by either the BSD license, or the Ruby license (or, by association +# with the Ruby license, the GPL). See the "doc" subdirectory of the Net::SSH +# distribution for the texts of these licenses. +# ----------------------------------------------------------------------------- +# net-ssh website : http://net-ssh.rubyforge.org +# project website: http://rubyforge.org/projects/net-ssh +# ============================================================================= +#++ + +module Net + module SSH + module Service + module AgentForward + + class Driver + + def initialize( connection, buffers, log, agent ) + @connection = connection + @buffers = buffers + @log = log + @agent = agent + @data = '' + + @connection.add_channel_open_handler( + "auth-agent@openssh.com", &method(:do_open_channel) ) + end + + def request + @connection.channel_request( 'auth-agent-req@openssh.com' ) + end + + def do_open_channel( connection, channel, data ) + channel.on_data &method(:do_data) + end + + # handle CHANNEL_DATA packets received on the agent-forward + # channel - pass complete received packets to the agent. + def do_data( channel, data ) + @data = @data + data + reply = call_agent + if reply + channel.send_data reply + @data = '' + end + end + + # Called if we have any data to forward to the + # agent. Examines the accumulated data to see if we have a + # complete packet, based on the length field (the first four + # bytes as a network long). + def call_agent + # if we have enough data to check the length of this packet + if @data.length >= 4 + packet_length = @data[0..3].unpack('N').first + # send the complete packet to the agent and read the + # response + if @data.length == (4 + packet_length) + @agent.send_raw_packet @data + buffer = @agent.read_raw_packet + end + end + buffer + end + + end + + end # AgentForward + end # Service + end # SSH +end # Net diff -urN --exclude '*~' net-ssh-1.0.10/lib/net/ssh/service/agentforward/services.rb net-ssh-1.0.10-agentforward/lib/net/ssh/service/agentforward/services.rb --- net-ssh-1.0.10/lib/net/ssh/service/agentforward/services.rb 1970-01-01 01:00:00.000000000 +0100 +++ net-ssh-1.0.10-agentforward/lib/net/ssh/service/agentforward/services.rb 2007-03-24 12:35:15.501752897 +0000 @@ -0,0 +1,41 @@ +#-- +# ============================================================================= +# Copyright (c) 2007, Chris Andrews (chris@nodnol.org), +# Jamis Buck (jgb3@email.byu.edu) +# All rights reserved. +# +# This source file is distributed as part of the Net::SSH Secure Shell Client +# library for Ruby. This file (and the library as a whole) may be used only as +# allowed by either the BSD license, or the Ruby license (or, by association +# with the Ruby license, the GPL). See the "doc" subdirectory of the Net::SSH +# distribution for the texts of these licenses. +# ----------------------------------------------------------------------------- +# net-ssh website : http://net-ssh.rubyforge.org +# project website: http://rubyforge.org/projects/net-ssh +# ============================================================================= +#++ + +module Net + module SSH + module Service + module AgentForward + + def register_services( container ) + + container.namespace_define :agentforward do |ns| + ns.driver :model => :singleton_deferred do |c,p| + require 'net/ssh/service/agentforward/driver' + Driver.new( c[:connection][:driver], + c[:transport][:buffers], + c[:log_for, p], + c[:userauth].agent) + end + end + + end + module_function :register_services + + end + end + end +end diff -urN --exclude '*~' net-ssh-1.0.10/lib/net/ssh/service/services.rb net-ssh-1.0.10-agentforward/lib/net/ssh/service/services.rb --- net-ssh-1.0.10/lib/net/ssh/service/services.rb 2006-04-15 03:52:17.000000000 +0100 +++ net-ssh-1.0.10-agentforward/lib/net/ssh/service/services.rb 2007-03-22 19:59:46.366439598 +0000 @@ -34,12 +34,14 @@ ns.require "net/ssh/service/forward/services", "#{self}::Forward" ns.require "net/ssh/service/process/services", "#{self}::Process" ns.require "net/ssh/service/shell/services", "#{self}::Shell" + ns.require "net/ssh/service/agentforward/services", "#{self}::AgentForward" end # Add the services to the services hash. container.services[ :forward ] = container.service.forward.driver container.services[ :process ] = container.service.process.driver container.services[ :shell ] = container.service.shell.driver + container.services[ :agentforward ] = container.service.agentforward.driver # Register the external services and add them to the collection of # known services. diff -urN --exclude '*~' net-ssh-1.0.10/lib/net/ssh/session.rb net-ssh-1.0.10-agentforward/lib/net/ssh/session.rb --- net-ssh-1.0.10/lib/net/ssh/session.rb 2006-09-07 18:27:56.000000000 +0100 +++ net-ssh-1.0.10-agentforward/lib/net/ssh/session.rb 2007-03-24 12:32:28.161620033 +0000 @@ -92,6 +92,8 @@ # returned (and must be closed explicitly). def initialize( *args ) @open = false + @agent_forwarded = false + process_arguments( *args ) @registry.define do |b| @@ -152,6 +154,17 @@ sanity_check channel = @connection.open_channel( type, data ) channel.on_confirm_open(&block) + + # If we have an agent, and agent-forwarding is enabled, set up + # the forwarding. Do this once only, after the first channel + # is opened. + if @forward_agent && @registry[:userauth].agent + unless @agent_forwarded + agentforward.request + @agent_forwarded = true + end + end + channel end @@ -218,6 +231,7 @@ @keys = @options[ :keys ] @host_keys = @options[ :host_keys ] @auth_methods = @options[ :auth_methods ] + @forward_agent = @options[ :forward_agent ] || false @crypto_backend = @options.fetch( :crypto_backend, :ossl ) verbose = @options.fetch( :verbose, :warn ) @@ -238,7 +252,8 @@ Needle::Registry.new( @registry_options ) [ :keys, :host_keys, :auth_methods, :username, :password, - :crypto_backend, :registry_options, :container, :log, :verbose + :crypto_backend, :registry_options, :container, :log, :verbose, + :forward_agent ].each do |i| @options.delete i end diff -urN --exclude '*~' net-ssh-1.0.10/lib/net/ssh/userauth/agent.rb net-ssh-1.0.10-agentforward/lib/net/ssh/userauth/agent.rb --- net-ssh-1.0.10/lib/net/ssh/userauth/agent.rb 2006-04-15 03:52:17.000000000 +0100 +++ net-ssh-1.0.10-agentforward/lib/net/ssh/userauth/agent.rb 2007-03-22 20:01:27.320465563 +0000 @@ -204,6 +204,17 @@ end private :agent_failed + def send_raw_packet( data ) + @socket.send data, 0 + end + + def read_raw_packet + buffer = @socket.read( 4 ) + length = buffer.unpack( "N" ).first + buffer = buffer + @socket.read( length ) + buffer + end + end end diff -urN --exclude '*~' net-ssh-1.0.10/test/service/agentforward/tc_driver.rb net-ssh-1.0.10-agentforward/test/service/agentforward/tc_driver.rb --- net-ssh-1.0.10/test/service/agentforward/tc_driver.rb 1970-01-01 01:00:00.000000000 +0100 +++ net-ssh-1.0.10-agentforward/test/service/agentforward/tc_driver.rb 2007-03-24 12:36:46.303391213 +0000 @@ -0,0 +1,138 @@ +#-- +# ============================================================================= +# Copyright (c) 2007, Chris Andrews (chris@nodnol.org), +# Jamis Buck (jgb3@email.byu.edu) +# All rights reserved. +# +# This source file is distributed as part of the Net::SSH Secure Shell Client +# library for Ruby. This file (and the library as a whole) may be used only as +# allowed by either the BSD license, or the Ruby license (or, by association +# with the Ruby license, the GPL). See the "doc" subdirectory of the Net::SSH +# distribution for the texts of these licenses. +# ----------------------------------------------------------------------------- +# net-ssh website : http://net-ssh.rubyforge.org +# project website: http://rubyforge.org/projects/net-ssh +# ============================================================================= +#++ + +$:.unshift "#{File.dirname(__FILE__)}/../../../lib" + +require 'net/ssh/service/agentforward/driver' +require 'net/ssh/util/buffer' +require 'test/unit' +require 'socket' + +class TC_AgentForward_Driver < Test::Unit::TestCase + + class Net::SSH::Service::AgentForward::Driver + attr_reader :data + end + + class Buffers + def writer + Net::SSH::Util::WriterBuffer.new + end + end + + class Log + def debug? + true + end + def debug(msg) + end + end + + class Agent + attr_reader :sends + def initialize + @sends = [] + end + def read_raw_packet + "raw agent data" + end + def send_raw_packet(data) + @sends.push data + end + end + + class MockObject + attr_reader :events + attr_reader :blocks + attr_reader :returns + + def initialize + @events = [] + @blocks = [] + @returns = [] + end + def method_missing( sym, *args, &block ) + @blocks << block + @events << [ sym, args, !block.nil? ] + @returns << MockObject.new + @returns.last + end + def method( sym ) + @events << [ :method, [ sym ], false ] + lambda { || } + end + def respond_to?( sym ) + true + end + end + + def setup + @connection = MockObject.new + @agent = Agent.new + + @driver = Net::SSH::Service::AgentForward::Driver.new( @connection, + Buffers.new, Log.new, @agent ) + end + + def test_initialize + assert_equal [ [:add_channel_open_handler, [ "auth-agent@openssh.com" ], true] ], + @connection.events + end + + def test_request + @driver.request + assert_equal 2, @connection.events.length + assert_equal [:channel_request, ["auth-agent-req@openssh.com"], false], + @connection.events[1] + end + + def test_do_open_channel + connection = MockObject.new + channel = MockObject.new + @driver.do_open_channel(connection, channel, nil) + assert_equal [ [:on_data, [], true] ], + channel.events + end + + def test_do_data_complete_packet + channel = MockObject.new + data = "\000\000\000\001v" + @driver.do_data(channel, data) + assert_equal [ [:send_data, ["raw agent data"], false ] ], + channel.events + assert_equal [ data ], + @agent.sends + assert_equal '', @driver.data + end + + def test_do_data_incomplete_packet + channel = MockObject.new + + @driver.do_data(channel, "\000\000\000\001") + assert_equal 0, channel.events.length + assert_equal 0, @agent.sends.length + assert_equal @driver.data, "\000\000\000\001" + + @driver.do_data(channel, "v") + assert_equal [ [:send_data, ["raw agent data"], false ] ], + channel.events + assert_equal [ "\000\000\000\001v" ], + @agent.sends + assert_equal '', @driver.data + end + +end