tpkg 2.3.3 → 2.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (205) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +20 -0
  5. data/Portfile +39 -0
  6. data/Portfile.template +39 -0
  7. data/README.md +43 -0
  8. data/Rakefile +468 -18
  9. data/bin/gem2tpkg +2 -2
  10. data/bin/tpkg +18 -13
  11. data/bin/tpkg_uploader +132 -0
  12. data/ca.pem +1 -0
  13. data/control +7 -0
  14. data/depend +3 -0
  15. data/externals-etch/authorized_keys +40 -0
  16. data/externals-etch/group +9 -0
  17. data/externals-etch/iptables +38 -0
  18. data/externals-etch/limits +30 -0
  19. data/externals-etch/nfs +30 -0
  20. data/externals-etch/sudo +30 -0
  21. data/externals-etch/supplemental_groups +8 -0
  22. data/externals-etch/sysctl +30 -0
  23. data/externals-etch/user +41 -0
  24. data/externals/group +39 -0
  25. data/externals/supplemental_groups +48 -0
  26. data/externals/user +39 -0
  27. data/lib/tpkg.rb +260 -991
  28. data/lib/tpkg/os.rb +164 -0
  29. data/lib/tpkg/os/debian.rb +159 -0
  30. data/lib/tpkg/os/freebsd.rb +113 -0
  31. data/lib/tpkg/os/macosx.rb +113 -0
  32. data/lib/tpkg/os/redhat.rb +173 -0
  33. data/lib/tpkg/os/solaris.rb +101 -0
  34. data/lib/tpkg/os/windows.rb +26 -0
  35. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify.rb +67 -0
  36. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/errors.rb +127 -0
  37. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/kwalify.schema.yaml +58 -0
  38. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/main.rb +442 -0
  39. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/messages.rb +173 -0
  40. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/meta-validator.rb +275 -0
  41. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/parser/base.rb +127 -0
  42. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/parser/yaml.rb +841 -0
  43. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/rule.rb +559 -0
  44. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/templates/genclass-java.eruby +222 -0
  45. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/templates/genclass-php.eruby +104 -0
  46. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/templates/genclass-ruby.eruby +113 -0
  47. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/types.rb +156 -0
  48. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/util.rb +158 -0
  49. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/util/assert-text-equal.rb +46 -0
  50. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/util/hash-interface.rb +18 -0
  51. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/util/hashlike.rb +51 -0
  52. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/util/option-parser.rb +220 -0
  53. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/util/ordered-hash.rb +57 -0
  54. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/util/testcase-helper.rb +112 -0
  55. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/validator.rb +282 -0
  56. data/lib/tpkg/thirdparty/kwalify-0.7.2/lib/kwalify/yaml-parser.rb +870 -0
  57. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh.rb +219 -0
  58. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/authentication/agent.rb +179 -0
  59. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/authentication/constants.rb +18 -0
  60. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/authentication/key_manager.rb +219 -0
  61. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/authentication/methods/abstract.rb +60 -0
  62. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/authentication/methods/hostbased.rb +71 -0
  63. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/authentication/methods/keyboard_interactive.rb +66 -0
  64. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/authentication/methods/password.rb +39 -0
  65. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/authentication/methods/publickey.rb +92 -0
  66. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/authentication/pageant.rb +183 -0
  67. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/authentication/session.rb +134 -0
  68. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/buffer.rb +340 -0
  69. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/buffered_io.rb +198 -0
  70. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/config.rb +205 -0
  71. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/connection/channel.rb +630 -0
  72. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/connection/constants.rb +33 -0
  73. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/connection/session.rb +597 -0
  74. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/connection/term.rb +178 -0
  75. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/errors.rb +85 -0
  76. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/key_factory.rb +102 -0
  77. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/known_hosts.rb +129 -0
  78. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/loggable.rb +61 -0
  79. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/packet.rb +102 -0
  80. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/prompt.rb +93 -0
  81. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/proxy/command.rb +75 -0
  82. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/proxy/errors.rb +14 -0
  83. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/proxy/http.rb +94 -0
  84. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/proxy/socks4.rb +70 -0
  85. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/proxy/socks5.rb +142 -0
  86. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/ruby_compat.rb +43 -0
  87. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/service/forward.rb +288 -0
  88. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/test.rb +89 -0
  89. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/test/channel.rb +129 -0
  90. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/test/extensions.rb +152 -0
  91. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/test/kex.rb +44 -0
  92. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/test/local_packet.rb +51 -0
  93. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/test/packet.rb +81 -0
  94. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/test/remote_packet.rb +38 -0
  95. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/test/script.rb +157 -0
  96. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/test/socket.rb +64 -0
  97. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/algorithms.rb +384 -0
  98. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/cipher_factory.rb +97 -0
  99. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/constants.rb +30 -0
  100. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/hmac.rb +31 -0
  101. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/hmac/abstract.rb +79 -0
  102. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/hmac/md5.rb +12 -0
  103. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/hmac/md5_96.rb +11 -0
  104. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/hmac/none.rb +15 -0
  105. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/hmac/sha1.rb +13 -0
  106. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/hmac/sha1_96.rb +11 -0
  107. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/identity_cipher.rb +55 -0
  108. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/kex.rb +13 -0
  109. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +208 -0
  110. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +77 -0
  111. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/openssl.rb +127 -0
  112. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/packet_stream.rb +235 -0
  113. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/server_version.rb +71 -0
  114. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/session.rb +276 -0
  115. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/transport/state.rb +206 -0
  116. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/verifiers/lenient.rb +30 -0
  117. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/verifiers/null.rb +12 -0
  118. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/verifiers/strict.rb +53 -0
  119. data/lib/tpkg/thirdparty/net-ssh-2.1.0/lib/net/ssh/version.rb +62 -0
  120. data/lib/tpkg/version.rb +3 -0
  121. data/man/man1/cpan2tpkg.1 +82 -0
  122. data/man/man1/gem2tpkg.1 +120 -0
  123. data/man/man1/tpkg.1 +411 -0
  124. data/pkginfo +8 -0
  125. data/postinstall.solaris +11 -0
  126. data/postremove.solaris +16 -0
  127. data/schema/schema-1.0.5.yml +0 -0
  128. data/schema/schema-1.0.6.yml +0 -0
  129. data/schema/schema-1.0.7.yml +0 -0
  130. data/schema/schema-1.0.8.yml +0 -0
  131. data/schema/schema-1.0.9.yml +0 -0
  132. data/schema/schema.yml +0 -0
  133. data/schema/tpkg-1.0.0.dtd +0 -0
  134. data/schema/tpkg-1.0.1.dtd +0 -0
  135. data/schema/tpkg-1.0.2.dtd +0 -0
  136. data/schema/tpkg-1.0.3.dtd +0 -0
  137. data/schema/tpkg-1.0.4.dtd +0 -0
  138. data/schema/tpkg-1.0.5.dtd +0 -0
  139. data/schema/tpkg-1.0.6.dtd +0 -0
  140. data/schema/tpkg-1.0.7.dtd +0 -0
  141. data/schema/tpkg-1.0.8.dtd +0 -0
  142. data/schema/tpkg-1.0.9.dtd +0 -0
  143. data/schema/tpkg.dtd +0 -0
  144. data/test/TODO +30 -0
  145. data/test/premadetestpkg/pkg_without_file_metadata-1.0-1.tpkg +0 -0
  146. data/test/test_checksum.rb +53 -0
  147. data/test/test_compress.rb +55 -0
  148. data/test/test_conflict.rb +41 -0
  149. data/test/test_crontabs.rb +398 -0
  150. data/test/test_dependency.rb +1113 -0
  151. data/test/test_downgrade.rb +80 -0
  152. data/test/test_download.rb +95 -0
  153. data/test/test_encrypt.rb +136 -0
  154. data/test/test_filemetadata.rb +131 -0
  155. data/test/test_initscript.rb +93 -0
  156. data/test/test_install.rb +186 -0
  157. data/test/test_lock.rb +82 -0
  158. data/test/test_make.rb +410 -0
  159. data/test/test_metadata.rb +805 -0
  160. data/test/test_misc.rb +379 -0
  161. data/test/test_options.rb +1711 -0
  162. data/test/test_os.rb +193 -0
  163. data/test/test_os_debian.rb +99 -0
  164. data/test/test_os_freebsd.rb +89 -0
  165. data/test/test_os_macosx.rb +79 -0
  166. data/test/test_os_redhat.rb +124 -0
  167. data/test/test_os_solaris.rb +85 -0
  168. data/test/test_os_windows.rb +26 -0
  169. data/test/test_query.rb +134 -0
  170. data/test/test_remove.rb +539 -0
  171. data/test/test_tar.rb +99 -0
  172. data/test/test_unpack.rb +977 -0
  173. data/test/test_upgrade.rb +398 -0
  174. data/test/test_version.rb +54 -0
  175. data/test/testcmds/crontab +14 -0
  176. data/test/testcmds/debian/apt-cache +145 -0
  177. data/test/testcmds/debian/dpkg-query +16 -0
  178. data/test/testcmds/freebsd/pkg_info +13 -0
  179. data/test/testcmds/macosx/port +35 -0
  180. data/test/testcmds/redhat/rpmbuild +6 -0
  181. data/test/testcmds/redhat/yum +90 -0
  182. data/test/testcmds/solaris/pkginfo +25 -0
  183. data/test/testcmds/solaris/pkgutil +36 -0
  184. data/test/testpkg/reloc/encfile +2 -0
  185. data/test/testpkg/reloc/file +2 -0
  186. data/test/testpkg/reloc/precryptfile +1 -0
  187. data/test/testpkg/reloc/precryptfile.plaintext +3 -0
  188. data/test/testpkg/tpkg-bad-ownergroup.xml +25 -0
  189. data/test/testpkg/tpkg-bad-ownergroup.yml +18 -0
  190. data/test/testpkg/tpkg-default-perms.xml +28 -0
  191. data/test/testpkg/tpkg-default-perms.yml +20 -0
  192. data/test/testpkg/tpkg-good-ownergroup.xml +25 -0
  193. data/test/testpkg/tpkg-good-ownergroup.yml +18 -0
  194. data/test/testpkg/tpkg-nativedeps.yml +13 -0
  195. data/test/testpkg/tpkg-nofiles.xml +14 -0
  196. data/test/testpkg/tpkg-nofiles.yml +9 -0
  197. data/test/testpkg/tpkg.xml +35 -0
  198. data/test/testpkg/tpkg.yml +25 -0
  199. data/test/tpkgtest.rb +300 -0
  200. data/tpkg.conf +16 -0
  201. data/tpkg.gemspec +24 -0
  202. data/tpkg.spec +28 -0
  203. data/tpkg.xml +17 -0
  204. data/tpkg_profile.sh +32 -0
  205. metadata +306 -31
@@ -0,0 +1,198 @@
1
+ require 'net/ssh/buffer'
2
+ require 'net/ssh/loggable'
3
+ require 'net/ssh/ruby_compat'
4
+
5
+ module Net; module SSH
6
+
7
+ # This module is used to extend sockets and other IO objects, to allow
8
+ # them to be buffered for both read and write. This abstraction makes it
9
+ # quite easy to write a select-based event loop
10
+ # (see Net::SSH::Connection::Session#listen_to).
11
+ #
12
+ # The general idea is that instead of calling #read directly on an IO that
13
+ # has been extended with this module, you call #fill (to add pending input
14
+ # to the internal read buffer), and then #read_available (to read from that
15
+ # buffer). Likewise, you don't call #write directly, you call #enqueue to
16
+ # add data to the write buffer, and then #send_pending or #wait_for_pending_sends
17
+ # to actually send the data across the wire.
18
+ #
19
+ # In this way you can easily use the object as an argument to IO.select,
20
+ # calling #fill when it is available for read, or #send_pending when it is
21
+ # available for write, and then call #enqueue and #read_available during
22
+ # the idle times.
23
+ #
24
+ # socket = TCPSocket.new(address, port)
25
+ # socket.extend(Net::SSH::BufferedIo)
26
+ #
27
+ # ssh.listen_to(socket)
28
+ #
29
+ # ssh.loop do
30
+ # if socket.available > 0
31
+ # puts socket.read_available
32
+ # socket.enqueue("response\n")
33
+ # end
34
+ # end
35
+ #
36
+ # Note that this module must be used to extend an instance, and should not
37
+ # be included in a class. If you do want to use it via an include, then you
38
+ # must make sure to invoke the private #initialize_buffered_io method in
39
+ # your class' #initialize method:
40
+ #
41
+ # class Foo < IO
42
+ # include Net::SSH::BufferedIo
43
+ #
44
+ # def initialize
45
+ # initialize_buffered_io
46
+ # # ...
47
+ # end
48
+ # end
49
+ module BufferedIo
50
+ include Loggable
51
+
52
+ # Called when the #extend is called on an object, with this module as the
53
+ # argument. It ensures that the modules instance variables are all properly
54
+ # initialized.
55
+ def self.extended(object) #:nodoc:
56
+ # need to use __send__ because #send is overridden in Socket
57
+ object.__send__(:initialize_buffered_io)
58
+ end
59
+
60
+ # Tries to read up to +n+ bytes of data from the remote end, and appends
61
+ # the data to the input buffer. It returns the number of bytes read, or 0
62
+ # if no data was available to be read.
63
+ def fill(n=8192)
64
+ input.consume!
65
+ data = recv(n)
66
+ debug { "read #{data.length} bytes" }
67
+ input.append(data)
68
+ return data.length
69
+ end
70
+
71
+ # Read up to +length+ bytes from the input buffer. If +length+ is nil,
72
+ # all available data is read from the buffer. (See #available.)
73
+ def read_available(length=nil)
74
+ input.read(length || available)
75
+ end
76
+
77
+ # Returns the number of bytes available to be read from the input buffer.
78
+ # (See #read_available.)
79
+ def available
80
+ input.available
81
+ end
82
+
83
+ # Enqueues data in the output buffer, to be written when #send_pending
84
+ # is called. Note that the data is _not_ sent immediately by this method!
85
+ def enqueue(data)
86
+ output.append(data)
87
+ end
88
+
89
+ # Returns +true+ if there is data waiting in the output buffer, and
90
+ # +false+ otherwise.
91
+ def pending_write?
92
+ output.length > 0
93
+ end
94
+
95
+ # Sends as much of the pending output as possible. Returns +true+ if any
96
+ # data was sent, and +false+ otherwise.
97
+ def send_pending
98
+ if output.length > 0
99
+ sent = send(output.to_s, 0)
100
+ debug { "sent #{sent} bytes" }
101
+ output.consume!(sent)
102
+ return sent > 0
103
+ else
104
+ return false
105
+ end
106
+ end
107
+
108
+ # Calls #send_pending repeatedly, if necessary, blocking until the output
109
+ # buffer is empty.
110
+ def wait_for_pending_sends
111
+ send_pending
112
+ while output.length > 0
113
+ result = Net::SSH::Compat.io_select(nil, [self]) or next
114
+ next unless result[1].any?
115
+ send_pending
116
+ end
117
+ end
118
+
119
+ public # these methods are primarily for use in tests
120
+
121
+ def write_buffer #:nodoc:
122
+ output.to_s
123
+ end
124
+
125
+ def read_buffer #:nodoc:
126
+ input.to_s
127
+ end
128
+
129
+ private
130
+
131
+ #--
132
+ # Can't use attr_reader here (after +private+) without incurring the
133
+ # wrath of "ruby -w". We hates it.
134
+ #++
135
+
136
+ def input; @input; end
137
+ def output; @output; end
138
+
139
+ # Initializes the intput and output buffers for this object. This method
140
+ # is called automatically when the module is mixed into an object via
141
+ # Object#extend (see Net::SSH::BufferedIo.extended), but must be called
142
+ # explicitly in the +initialize+ method of any class that uses
143
+ # Module#include to add this module.
144
+ def initialize_buffered_io
145
+ @input = Net::SSH::Buffer.new
146
+ @output = Net::SSH::Buffer.new
147
+ end
148
+ end
149
+
150
+
151
+
152
+ # Fixes for two issues by Miklós Fazekas:
153
+ #
154
+ # * if client closes a forwarded connection, but the server is
155
+ # reading, net-ssh terminates with IOError socket closed.
156
+ # * if client force closes (RST) a forwarded connection, but
157
+ # server is reading, net-ssh terminates with [an exception]
158
+ #
159
+ # See:
160
+ #
161
+ # http://net-ssh.lighthouseapp.com/projects/36253/tickets/7
162
+ # http://github.com/net-ssh/net-ssh/tree/portfwfix
163
+ #
164
+ module ForwardedBufferedIo
165
+ def fill(n=8192)
166
+ begin
167
+ super(n)
168
+ rescue Errno::ECONNRESET => e
169
+ debug { "connection was reset => shallowing exception:#{e}" }
170
+ return 0
171
+ rescue IOError => e
172
+ if e.message =~ /closed/ then
173
+ debug { "connection was reset => shallowing exception:#{e}" }
174
+ return 0
175
+ else
176
+ raise
177
+ end
178
+ end
179
+ end
180
+
181
+ def send_pending
182
+ begin
183
+ super
184
+ rescue Errno::ECONNRESET => e
185
+ debug { "connection was reset => shallowing exception:#{e}" }
186
+ return 0
187
+ rescue IOError => e
188
+ if e.message =~ /closed/ then
189
+ debug { "connection was reset => shallowing exception:#{e}" }
190
+ return 0
191
+ else
192
+ raise
193
+ end
194
+ end
195
+ end
196
+ end
197
+
198
+ end; end
@@ -0,0 +1,205 @@
1
+ module Net; module SSH
2
+
3
+ # The Net::SSH::Config class is used to parse OpenSSH configuration files,
4
+ # and translates that syntax into the configuration syntax that Net::SSH
5
+ # understands. This lets Net::SSH scripts read their configuration (to
6
+ # some extent) from OpenSSH configuration files (~/.ssh/config, /etc/ssh_config,
7
+ # and so forth).
8
+ #
9
+ # Only a subset of OpenSSH configuration options are understood:
10
+ #
11
+ # * Ciphers => maps to the :encryption option
12
+ # * Compression => :compression
13
+ # * CompressionLevel => :compression_level
14
+ # * ConnectTimeout => maps to the :timeout option
15
+ # * ForwardAgent => :forward_agent
16
+ # * GlobalKnownHostsFile => :global_known_hosts_file
17
+ # * HostBasedAuthentication => maps to the :auth_methods option
18
+ # * HostKeyAlgorithms => maps to :host_key option
19
+ # * HostKeyAlias => :host_key_alias
20
+ # * HostName => :host_name
21
+ # * IdentityFile => maps to the :keys option
22
+ # * IdentitiesOnly => :keys_only
23
+ # * Macs => maps to the :hmac option
24
+ # * PasswordAuthentication => maps to the :auth_methods option
25
+ # * Port => :port
26
+ # * PreferredAuthentications => maps to the :auth_methods option
27
+ # * ProxyCommand => maps to the :proxy option
28
+ # * RekeyLimit => :rekey_limit
29
+ # * User => :user
30
+ # * UserKnownHostsFile => :user_known_hosts_file
31
+ #
32
+ # Note that you will never need to use this class directly--you can control
33
+ # whether the OpenSSH configuration files are read by passing the :config
34
+ # option to Net::SSH.start. (They are, by default.)
35
+ class Config
36
+ class << self
37
+ @@default_files = %w(~/.ssh/config /etc/ssh_config /etc/ssh/ssh_config)
38
+
39
+ # Returns an array of locations of OpenSSH configuration files
40
+ # to parse by default.
41
+ def default_files
42
+ @@default_files
43
+ end
44
+
45
+ # Loads the configuration data for the given +host+ from all of the
46
+ # given +files+ (defaulting to the list of files returned by
47
+ # #default_files), translates the resulting hash into the options
48
+ # recognized by Net::SSH, and returns them.
49
+ def for(host, files=default_files)
50
+ translate(files.inject({}) { |settings, file| load(file, host, settings) })
51
+ end
52
+
53
+ # Load the OpenSSH configuration settings in the given +file+ for the
54
+ # given +host+. If +settings+ is given, the options are merged into
55
+ # that hash, with existing values taking precedence over newly parsed
56
+ # ones. Returns a hash containing the OpenSSH options. (See
57
+ # #translate for how to convert the OpenSSH options into Net::SSH
58
+ # options.)
59
+ def load(path, host, settings={})
60
+ file = File.expand_path(path)
61
+ return settings unless File.readable?(file)
62
+
63
+ globals = {}
64
+ matched_host = nil
65
+ multi_host = []
66
+ seen_host = false
67
+ IO.foreach(file) do |line|
68
+ next if line =~ /^\s*(?:#.*)?$/
69
+
70
+ if line =~ /^\s*(\S+)\s*=(.*)$/
71
+ key, value = $1, $2
72
+ else
73
+ key, value = line.strip.split(/\s+/, 2)
74
+ end
75
+
76
+ # silently ignore malformed entries
77
+ next if value.nil?
78
+
79
+ key.downcase!
80
+ value = $1 if value =~ /^"(.*)"$/
81
+
82
+ value = case value.strip
83
+ when /^\d+$/ then value.to_i
84
+ when /^no$/i then false
85
+ when /^yes$/i then true
86
+ else value
87
+ end
88
+
89
+ if key == 'host'
90
+ # Support "Host host1 host2 hostN".
91
+ # See http://github.com/net-ssh/net-ssh/issues#issue/6
92
+ multi_host = value.to_s.split(/\s+/)
93
+ matched_host = multi_host.select { |h| host =~ pattern2regex(h) }.first
94
+ seen_host = true
95
+ elsif !seen_host
96
+ if key == 'identityfile'
97
+ (globals[key] ||= []) << value
98
+ else
99
+ globals[key] = value unless settings.key?(key)
100
+ end
101
+ elsif !matched_host.nil?
102
+ if key == 'identityfile'
103
+ (settings[key] ||= []) << value
104
+ else
105
+ settings[key] = value unless settings.key?(key)
106
+ end
107
+ end
108
+ end
109
+
110
+ settings = globals.merge(settings) if globals
111
+
112
+ return settings
113
+ end
114
+
115
+ # Given a hash of OpenSSH configuration options, converts them into
116
+ # a hash of Net::SSH options. Unrecognized options are ignored. The
117
+ # +settings+ hash must have Strings for keys, all downcased, and
118
+ # the returned hash will have Symbols for keys.
119
+ def translate(settings)
120
+ settings.inject({}) do |hash, (key, value)|
121
+ case key
122
+ when 'ciphers' then
123
+ hash[:encryption] = value.split(/,/)
124
+ when 'compression' then
125
+ hash[:compression] = value
126
+ when 'compressionlevel' then
127
+ hash[:compression_level] = value
128
+ when 'connecttimeout' then
129
+ hash[:timeout] = value
130
+ when 'forwardagent' then
131
+ hash[:forward_agent] = value
132
+ when 'identitiesonly' then
133
+ hash[:keys_only] = value
134
+ when 'globalknownhostsfile'
135
+ hash[:global_known_hosts_file] = value
136
+ when 'hostbasedauthentication' then
137
+ if value
138
+ hash[:auth_methods] ||= []
139
+ hash[:auth_methods] << "hostbased"
140
+ end
141
+ when 'hostkeyalgorithms' then
142
+ hash[:host_key] = value.split(/,/)
143
+ when 'hostkeyalias' then
144
+ hash[:host_key_alias] = value
145
+ when 'hostname' then
146
+ hash[:host_name] = value
147
+ when 'identityfile' then
148
+ hash[:keys] = value
149
+ when 'macs' then
150
+ hash[:hmac] = value.split(/,/)
151
+ when 'passwordauthentication'
152
+ if value
153
+ hash[:auth_methods] ||= []
154
+ hash[:auth_methods] << "password"
155
+ end
156
+ when 'port'
157
+ hash[:port] = value
158
+ when 'preferredauthentications'
159
+ hash[:auth_methods] = value.split(/,/)
160
+ when 'proxycommand'
161
+ if value and !(value =~ /^none$/)
162
+ require 'net/ssh/proxy/command'
163
+ hash[:proxy] = Net::SSH::Proxy::Command.new(value)
164
+ end
165
+ when 'pubkeyauthentication'
166
+ if value
167
+ hash[:auth_methods] ||= []
168
+ hash[:auth_methods] << "publickey"
169
+ end
170
+ when 'rekeylimit'
171
+ hash[:rekey_limit] = interpret_size(value)
172
+ when 'user'
173
+ hash[:user] = value
174
+ when 'userknownhostsfile'
175
+ hash[:user_known_hosts_file] = value
176
+ end
177
+ hash
178
+ end
179
+ end
180
+
181
+ private
182
+
183
+ # Converts an ssh_config pattern into a regex for matching against
184
+ # host names.
185
+ def pattern2regex(pattern)
186
+ pattern = "^" + pattern.to_s.gsub(/\./, "\\.").
187
+ gsub(/\?/, '.').
188
+ gsub(/([+\/])/, '\\\\\\0').
189
+ gsub(/\*/, '.*') + "$"
190
+ Regexp.new(pattern, true)
191
+ end
192
+
193
+ # Converts the given size into an integer number of bytes.
194
+ def interpret_size(size)
195
+ case size
196
+ when /k$/i then size.to_i * 1024
197
+ when /m$/i then size.to_i * 1024 * 1024
198
+ when /g$/i then size.to_i * 1024 * 1024 * 1024
199
+ else size.to_i
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ end; end
@@ -0,0 +1,630 @@
1
+ require 'net/ssh/loggable'
2
+ require 'net/ssh/connection/constants'
3
+ require 'net/ssh/connection/term'
4
+
5
+ module Net; module SSH; module Connection
6
+
7
+ # The channel abstraction. Multiple "channels" can be multiplexed onto a
8
+ # single SSH channel, each operating independently and seemingly in parallel.
9
+ # This class represents a single such channel. Most operations performed
10
+ # with the Net::SSH library will involve using one or more channels.
11
+ #
12
+ # Channels are intended to be used asynchronously. You request that one be
13
+ # opened (via Connection::Session#open_channel), and when it is opened, your
14
+ # callback is invoked. Then, you set various other callbacks on the newly
15
+ # opened channel, which are called in response to the corresponding events.
16
+ # Programming with Net::SSH works best if you think of your programs as
17
+ # state machines. Complex programs are best implemented as objects that
18
+ # wrap a channel. See Net::SCP and Net::SFTP for examples of how complex
19
+ # state machines can be built on top of the SSH protocol.
20
+ #
21
+ # ssh.open_channel do |channel|
22
+ # channel.exec("/invoke/some/command") do |ch, success|
23
+ # abort "could not execute command" unless success
24
+ #
25
+ # channel.on_data do |ch, data|
26
+ # puts "got stdout: #{data}"
27
+ # channel.send_data "something for stdin\n"
28
+ # end
29
+ #
30
+ # channel.on_extended_data do |ch, type, data|
31
+ # puts "got stderr: #{data}"
32
+ # end
33
+ #
34
+ # channel.on_close do |ch|
35
+ # puts "channel is closing!"
36
+ # end
37
+ # end
38
+ # end
39
+ #
40
+ # ssh.loop
41
+ #
42
+ # Channels also have a basic hash-like interface, that allows programs to
43
+ # store arbitrary state information on a channel object. This helps simplify
44
+ # the writing of state machines, especially when you may be juggling
45
+ # multiple open channels at the same time.
46
+ #
47
+ # Note that data sent across SSH channels are governed by maximum packet
48
+ # sizes and maximum window sizes. These details are managed internally
49
+ # by Net::SSH::Connection::Channel, so you may remain blissfully ignorant
50
+ # if you so desire, but you can always inspect the current maximums, as
51
+ # well as the remaining window size, using the reader attributes for those
52
+ # values.
53
+ class Channel
54
+ include Constants, Loggable
55
+
56
+ # The local id for this channel, assigned by the Net::SSH::Connection::Session instance.
57
+ attr_reader :local_id
58
+
59
+ # The remote id for this channel, assigned by the remote host.
60
+ attr_reader :remote_id
61
+
62
+ # The type of this channel, usually "session".
63
+ attr_reader :type
64
+
65
+ # The underlying Net::SSH::Connection::Session instance that supports this channel.
66
+ attr_reader :connection
67
+
68
+ # The maximum packet size that the local host can receive.
69
+ attr_reader :local_maximum_packet_size
70
+
71
+ # The maximum amount of data that the local end of this channel can
72
+ # receive. This is a total, not per-packet.
73
+ attr_reader :local_maximum_window_size
74
+
75
+ # The maximum packet size that the remote host can receive.
76
+ attr_reader :remote_maximum_packet_size
77
+
78
+ # The maximum amount of data that the remote end of this channel can
79
+ # receive. This is a total, not per-packet.
80
+ attr_reader :remote_maximum_window_size
81
+
82
+ # This is the remaining window size on the local end of this channel. When
83
+ # this reaches zero, no more data can be received.
84
+ attr_reader :local_window_size
85
+
86
+ # This is the remaining window size on the remote end of this channel. When
87
+ # this reaches zero, no more data can be sent.
88
+ attr_reader :remote_window_size
89
+
90
+ # A hash of properties for this channel. These can be used to store state
91
+ # information about this channel. See also #[] and #[]=.
92
+ attr_reader :properties
93
+
94
+ # The output buffer for this channel. Data written to the channel is
95
+ # enqueued here, to be written as CHANNEL_DATA packets during each pass of
96
+ # the event loop. See Connection::Session#process and #enqueue_pending_output.
97
+ attr_reader :output #:nodoc:
98
+
99
+ # The list of pending requests. Each time a request is sent which requires
100
+ # a reply, the corresponding callback is pushed onto this queue. As responses
101
+ # arrive, they are shifted off the front and handled.
102
+ attr_reader :pending_requests #:nodoc:
103
+
104
+ # Instantiates a new channel on the given connection, of the given type,
105
+ # and with the given id. If a block is given, it will be remembered until
106
+ # the channel is confirmed open by the server, and will be invoked at
107
+ # that time (see #do_open_confirmation).
108
+ #
109
+ # This also sets the default maximum packet size and maximum window size.
110
+ def initialize(connection, type, local_id, &on_confirm_open)
111
+ self.logger = connection.logger
112
+
113
+ @connection = connection
114
+ @type = type
115
+ @local_id = local_id
116
+
117
+ @local_maximum_packet_size = 0x10000
118
+ @local_window_size = @local_maximum_window_size = 0x20000
119
+
120
+ @on_confirm_open = on_confirm_open
121
+
122
+ @output = Buffer.new
123
+
124
+ @properties = {}
125
+
126
+ @pending_requests = []
127
+ @on_open_failed = @on_data = @on_extended_data = @on_process = @on_close = @on_eof = nil
128
+ @on_request = {}
129
+ @closing = @eof = @sent_eof = false
130
+ end
131
+
132
+ # A shortcut for accessing properties of the channel (see #properties).
133
+ def [](name)
134
+ @properties[name]
135
+ end
136
+
137
+ # A shortcut for setting properties of the channel (see #properties).
138
+ def []=(name, value)
139
+ @properties[name] = value
140
+ end
141
+
142
+ # Syntactic sugar for executing a command. Sends a channel request asking
143
+ # that the given command be invoked. If the block is given, it will be
144
+ # called when the server responds. The first parameter will be the
145
+ # channel, and the second will be true or false, indicating whether the
146
+ # request succeeded or not. In this case, success means that the command
147
+ # is being executed, not that it has completed, and failure means that the
148
+ # command altogether failed to be executed.
149
+ #
150
+ # channel.exec "ls -l /home" do |ch, success|
151
+ # if success
152
+ # puts "command has begun executing..."
153
+ # # this is a good place to hang callbacks like #on_data...
154
+ # else
155
+ # puts "alas! the command could not be invoked!"
156
+ # end
157
+ # end
158
+ def exec(command, &block)
159
+ send_channel_request("exec", :string, command, &block)
160
+ end
161
+
162
+ # Syntactic sugar for requesting that a subsystem be started. Subsystems
163
+ # are a way for other protocols (like SFTP) to be run, using SSH as
164
+ # the transport. Generally, you'll never need to call this directly unless
165
+ # you are the implementor of something that consumes an SSH subsystem, like
166
+ # SFTP.
167
+ #
168
+ # channel.subsystem("sftp") do |ch, success|
169
+ # if success
170
+ # puts "subsystem successfully started"
171
+ # else
172
+ # puts "subsystem could not be started"
173
+ # end
174
+ # end
175
+ def subsystem(subsystem, &block)
176
+ send_channel_request("subsystem", :string, subsystem, &block)
177
+ end
178
+
179
+ # Syntactic sugar for setting an environment variable in the remote
180
+ # process' environment. Note that for security reasons, the server may
181
+ # refuse to set certain environment variables, or all, at the server's
182
+ # discretion. If you are connecting to an OpenSSH server, you will
183
+ # need to update the AcceptEnv setting in the sshd_config to include the
184
+ # environment variables you want to send.
185
+ #
186
+ # channel.env "PATH", "/usr/local/bin"
187
+ def env(variable_name, variable_value, &block)
188
+ send_channel_request("env", :string, variable_name, :string, variable_value, &block)
189
+ end
190
+
191
+ # A hash of the valid PTY options (see #request_pty).
192
+ VALID_PTY_OPTIONS = { :term => "xterm",
193
+ :chars_wide => 80,
194
+ :chars_high => 24,
195
+ :pixels_wide => 640,
196
+ :pixels_high => 480,
197
+ :modes => {} }
198
+
199
+ # Requests that a pseudo-tty (or "pty") be made available for this channel.
200
+ # This is useful when you want to invoke and interact with some kind of
201
+ # screen-based program (e.g., vim, or some menuing system).
202
+ #
203
+ # Note, that without a pty some programs (e.g. sudo, or subversion) on
204
+ # some systems, will not be able to run interactively, and will error
205
+ # instead of prompt if they ever need some user interaction.
206
+ #
207
+ # Note, too, that when a pty is requested, user's shell configuration
208
+ # scripts (.bashrc and such) are not run by default, whereas they are
209
+ # run when a pty is not present.
210
+ #
211
+ # channel.request_pty do |ch, success|
212
+ # if success
213
+ # puts "pty successfully obtained"
214
+ # else
215
+ # puts "could not obtain pty"
216
+ # end
217
+ # end
218
+ def request_pty(opts={}, &block)
219
+ extra = opts.keys - VALID_PTY_OPTIONS.keys
220
+ raise ArgumentError, "invalid option(s) to request_pty: #{extra.inspect}" if extra.any?
221
+
222
+ opts = VALID_PTY_OPTIONS.merge(opts)
223
+
224
+ modes = opts[:modes].inject(Buffer.new) do |memo, (mode, data)|
225
+ memo.write_byte(mode).write_long(data)
226
+ end
227
+ # mark the end of the mode opcode list with a 0 byte
228
+ modes.write_byte(0)
229
+
230
+ send_channel_request("pty-req", :string, opts[:term],
231
+ :long, opts[:chars_wide], :long, opts[:chars_high],
232
+ :long, opts[:pixels_wide], :long, opts[:pixels_high],
233
+ :string, modes.to_s, &block)
234
+ end
235
+
236
+ # Sends data to the channel's remote endpoint. This usually has the
237
+ # effect of sending the given string to the remote process' stdin stream.
238
+ # Note that it does not immediately send the data across the channel,
239
+ # but instead merely appends the given data to the channel's output buffer,
240
+ # preparatory to being packaged up and sent out the next time the connection
241
+ # is accepting data. (A connection might not be accepting data if, for
242
+ # instance, it has filled its data window and has not yet been resized by
243
+ # the remote end-point.)
244
+ #
245
+ # This will raise an exception if the channel has previously declared
246
+ # that no more data will be sent (see #eof!).
247
+ #
248
+ # channel.send_data("the password\n")
249
+ def send_data(data)
250
+ raise EOFError, "cannot send data if channel has declared eof" if eof?
251
+ output.append(data.to_s)
252
+ end
253
+
254
+ # Returns true if the channel exists in the channel list of the session,
255
+ # and false otherwise. This can be used to determine whether a channel has
256
+ # been closed or not.
257
+ #
258
+ # ssh.loop { channel.active? }
259
+ def active?
260
+ connection.channels.key?(local_id)
261
+ end
262
+
263
+ # Runs the SSH event loop until the channel is no longer active. This is
264
+ # handy for blocking while you wait for some channel to finish.
265
+ #
266
+ # channel.exec("grep ...") { ... }
267
+ # channel.wait
268
+ def wait
269
+ connection.loop { active? }
270
+ end
271
+
272
+ # Returns true if the channel is currently closing, but not actually
273
+ # closed. A channel is closing when, for instance, #close has been
274
+ # invoked, but the server has not yet responded with a CHANNEL_CLOSE
275
+ # packet of its own.
276
+ def closing?
277
+ @closing
278
+ end
279
+
280
+ # Requests that the channel be closed. If the channel is already closing,
281
+ # this does nothing, nor does it do anything if the channel has not yet
282
+ # been confirmed open (see #do_open_confirmation). Otherwise, it sends a
283
+ # CHANNEL_CLOSE message and marks the channel as closing.
284
+ def close
285
+ return if @closing
286
+ if remote_id
287
+ @closing = true
288
+ connection.send_message(Buffer.from(:byte, CHANNEL_CLOSE, :long, remote_id))
289
+ end
290
+ end
291
+
292
+ # Returns true if the local end of the channel has declared that no more
293
+ # data is forthcoming (see #eof!). Trying to send data via #send_data when
294
+ # this is true will result in an exception being raised.
295
+ def eof?
296
+ @eof
297
+ end
298
+
299
+ # Tells the remote end of the channel that no more data is forthcoming
300
+ # from this end of the channel. The remote end may still send data.
301
+ # The CHANNEL_EOF packet will be sent once the output buffer is empty.
302
+ def eof!
303
+ return if eof?
304
+ @eof = true
305
+ end
306
+
307
+ # If an #on_process handler has been set up, this will cause it to be
308
+ # invoked (passing the channel itself as an argument). It also causes all
309
+ # pending output to be enqueued as CHANNEL_DATA packets (see #enqueue_pending_output).
310
+ def process
311
+ @on_process.call(self) if @on_process
312
+ enqueue_pending_output
313
+
314
+ if @eof and not @sent_eof and output.empty? and remote_id
315
+ connection.send_message(Buffer.from(:byte, CHANNEL_EOF, :long, remote_id))
316
+ @sent_eof = true
317
+ end
318
+ end
319
+
320
+ # Registers a callback to be invoked when data packets are received by the
321
+ # channel. The callback is called with the channel as the first argument,
322
+ # and the data as the second.
323
+ #
324
+ # channel.on_data do |ch, data|
325
+ # puts "got data: #{data.inspect}"
326
+ # end
327
+ #
328
+ # Data received this way is typically the data written by the remote
329
+ # process to its +stdout+ stream.
330
+ def on_data(&block)
331
+ old, @on_data = @on_data, block
332
+ old
333
+ end
334
+
335
+ # Registers a callback to be invoked when extended data packets are received
336
+ # by the channel. The callback is called with the channel as the first
337
+ # argument, the data type (as an integer) as the second, and the data as
338
+ # the third. Extended data is almost exclusively used to send +stderr+ data
339
+ # (+type+ == 1). Other extended data types are not defined by the SSH
340
+ # protocol.
341
+ #
342
+ # channel.on_extended_data do |ch, type, data|
343
+ # puts "got stderr: #{data.inspect}"
344
+ # end
345
+ def on_extended_data(&block)
346
+ old, @on_extended_data = @on_extended_data, block
347
+ old
348
+ end
349
+
350
+ # Registers a callback to be invoked for each pass of the event loop for
351
+ # this channel. There are no guarantees on timeliness in the event loop,
352
+ # but it will be called roughly once for each packet received by the
353
+ # connection (not the channel). This callback is invoked with the channel
354
+ # as the sole argument.
355
+ #
356
+ # Here's an example that accumulates the channel data into a variable on
357
+ # the channel itself, and displays individual lines in the input one
358
+ # at a time when the channel is processed:
359
+ #
360
+ # channel[:data] = ""
361
+ #
362
+ # channel.on_data do |ch, data|
363
+ # channel[:data] << data
364
+ # end
365
+ #
366
+ # channel.on_process do |ch|
367
+ # if channel[:data] =~ /^.*?\n/
368
+ # puts $&
369
+ # channel[:data] = $'
370
+ # end
371
+ # end
372
+ def on_process(&block)
373
+ old, @on_process = @on_process, block
374
+ old
375
+ end
376
+
377
+ # Registers a callback to be invoked when the server acknowledges that a
378
+ # channel is closed. This is invoked with the channel as the sole argument.
379
+ #
380
+ # channel.on_close do |ch|
381
+ # puts "remote end is closing!"
382
+ # end
383
+ def on_close(&block)
384
+ old, @on_close = @on_close, block
385
+ old
386
+ end
387
+
388
+ # Registers a callback to be invoked when the server indicates that no more
389
+ # data will be sent to the channel (although the channel can still send
390
+ # data to the server). The channel is the sole argument to the callback.
391
+ #
392
+ # channel.on_eof do |ch|
393
+ # puts "remote end is done sending data"
394
+ # end
395
+ def on_eof(&block)
396
+ old, @on_eof = @on_eof, block
397
+ old
398
+ end
399
+
400
+ # Registers a callback to be invoked when the server was unable to open
401
+ # the requested channel. The channel itself will be passed to the block,
402
+ # along with the integer "reason code" for the failure, and a textual
403
+ # description of the failure from the server.
404
+ #
405
+ # channel = session.open_channel do |ch|
406
+ # # ..
407
+ # end
408
+ #
409
+ # channel.on_open_failed { |ch, code, desc| ... }
410
+ def on_open_failed(&block)
411
+ old, @on_open_failed = @on_open_failed, block
412
+ old
413
+ end
414
+
415
+ # Registers a callback to be invoked when a channel request of the given
416
+ # type is received. The callback will receive the channel as the first
417
+ # argument, and the associated (unparsed) data as the second. The data
418
+ # will be a Net::SSH::Buffer that you will need to parse, yourself,
419
+ # according to the kind of request you are watching.
420
+ #
421
+ # By default, if the request wants a reply, Net::SSH will send a
422
+ # CHANNEL_SUCCESS response for any request that was handled by a registered
423
+ # callback, and CHANNEL_FAILURE for any that wasn't, but if you want your
424
+ # registered callback to result in a CHANNEL_FAILURE response, just raise
425
+ # Net::SSH::ChannelRequestFailed.
426
+ #
427
+ # Some common channel requests that your programs might want to listen
428
+ # for are:
429
+ #
430
+ # * "exit-status" : the exit status of the remote process will be reported
431
+ # as a long integer in the data buffer, which you can grab via
432
+ # data.read_long.
433
+ # * "exit-signal" : if the remote process died as a result of a signal
434
+ # being sent to it, the signal will be reported as a string in the
435
+ # data, via data.read_string. (Not all SSH servers support this channel
436
+ # request type.)
437
+ #
438
+ # channel.on_request "exit-status" do |ch, data|
439
+ # puts "process terminated with exit status: #{data.read_long}"
440
+ # end
441
+ def on_request(type, &block)
442
+ old, @on_request[type] = @on_request[type], block
443
+ old
444
+ end
445
+
446
+ # Sends a new channel request with the given name. The extra +data+
447
+ # parameter must either be empty, or consist of an even number of
448
+ # arguments. See Net::SSH::Buffer.from for a description of their format.
449
+ # If a block is given, it is registered as a callback for a pending
450
+ # request, and the packet will be flagged so that the server knows a
451
+ # reply is required. If no block is given, the server will send no
452
+ # response to this request. Responses, where required, will cause the
453
+ # callback to be invoked with the channel as the first argument, and
454
+ # either true or false as the second, depending on whether the request
455
+ # succeeded or not. The meaning of "success" and "failure" in this context
456
+ # is dependent on the specific request that was sent.
457
+ #
458
+ # channel.send_channel_request "shell" do |ch, success|
459
+ # if success
460
+ # puts "user shell started successfully"
461
+ # else
462
+ # puts "could not start user shell"
463
+ # end
464
+ # end
465
+ #
466
+ # Most channel requests you'll want to send are already wrapped in more
467
+ # convenient helper methods (see #exec and #subsystem).
468
+ def send_channel_request(request_name, *data, &callback)
469
+ info { "sending channel request #{request_name.inspect}" }
470
+ msg = Buffer.from(:byte, CHANNEL_REQUEST,
471
+ :long, remote_id, :string, request_name,
472
+ :bool, !callback.nil?, *data)
473
+ connection.send_message(msg)
474
+ pending_requests << callback if callback
475
+ end
476
+
477
+ public # these methods are public, but for Net::SSH internal use only
478
+
479
+ # Enqueues pending output at the connection as CHANNEL_DATA packets. This
480
+ # does nothing if the channel has not yet been confirmed open (see
481
+ # #do_open_confirmation). This is called automatically by #process, which
482
+ # is called from the event loop (Connection::Session#process). You will
483
+ # generally not need to invoke it directly.
484
+ def enqueue_pending_output #:nodoc:
485
+ return unless remote_id
486
+
487
+ while output.length > 0
488
+ length = output.length
489
+ length = remote_window_size if length > remote_window_size
490
+ length = remote_maximum_packet_size if length > remote_maximum_packet_size
491
+
492
+ if length > 0
493
+ connection.send_message(Buffer.from(:byte, CHANNEL_DATA, :long, remote_id, :string, output.read(length)))
494
+ output.consume!
495
+ @remote_window_size -= length
496
+ else
497
+ break
498
+ end
499
+ end
500
+ end
501
+
502
+ # Invoked when the server confirms that a channel has been opened.
503
+ # The remote_id is the id of the channel as assigned by the remote host,
504
+ # and max_window and max_packet are the maximum window and maximum
505
+ # packet sizes, respectively. If an open-confirmation callback was
506
+ # given when the channel was created, it is invoked at this time with
507
+ # the channel itself as the sole argument.
508
+ def do_open_confirmation(remote_id, max_window, max_packet) #:nodoc:
509
+ @remote_id = remote_id
510
+ @remote_window_size = @remote_maximum_window_size = max_window
511
+ @remote_maximum_packet_size = max_packet
512
+ connection.forward.agent(self) if connection.options[:forward_agent] && type == "session"
513
+ @on_confirm_open.call(self) if @on_confirm_open
514
+ end
515
+
516
+ # Invoked when the server failed to open the channel. If an #on_open_failed
517
+ # callback was specified, it will be invoked with the channel, reason code,
518
+ # and description as arguments. Otherwise, a ChannelOpenFailed exception
519
+ # will be raised.
520
+ def do_open_failed(reason_code, description)
521
+ if @on_open_failed
522
+ @on_open_failed.call(self, reason_code, description)
523
+ else
524
+ raise ChannelOpenFailed.new(reason_code, description)
525
+ end
526
+ end
527
+
528
+ # Invoked when the server sends a CHANNEL_WINDOW_ADJUST packet, and
529
+ # causes the remote window size to be adjusted upwards by the given
530
+ # number of bytes. This has the effect of allowing more data to be sent
531
+ # from the local end to the remote end of the channel.
532
+ def do_window_adjust(bytes) #:nodoc:
533
+ @remote_maximum_window_size += bytes
534
+ @remote_window_size += bytes
535
+ end
536
+
537
+ # Invoked when the server sends a channel request. If any #on_request
538
+ # callback has been registered for the specific type of this request,
539
+ # it is invoked. If +want_reply+ is true, a packet will be sent of
540
+ # either CHANNEL_SUCCESS or CHANNEL_FAILURE type. If there was no callback
541
+ # to handle the request, CHANNEL_FAILURE will be sent. Otherwise,
542
+ # CHANNEL_SUCCESS, unless the callback raised ChannelRequestFailed. The
543
+ # callback should accept the channel as the first argument, and the
544
+ # request-specific data as the second.
545
+ def do_request(request, want_reply, data) #:nodoc:
546
+ result = true
547
+
548
+ begin
549
+ callback = @on_request[request] or raise ChannelRequestFailed
550
+ callback.call(self, data)
551
+ rescue ChannelRequestFailed
552
+ result = false
553
+ end
554
+
555
+ if want_reply
556
+ msg = Buffer.from(:byte, result ? CHANNEL_SUCCESS : CHANNEL_FAILURE, :long, remote_id)
557
+ connection.send_message(msg)
558
+ end
559
+ end
560
+
561
+ # Invokes the #on_data callback when the server sends data to the
562
+ # channel. This will reduce the available window size on the local end,
563
+ # but does not actually throttle requests that come in illegally when
564
+ # the window size is too small. The callback is invoked with the channel
565
+ # as the first argument, and the data as the second.
566
+ def do_data(data) #:nodoc:
567
+ update_local_window_size(data.length)
568
+ @on_data.call(self, data) if @on_data
569
+ end
570
+
571
+ # Invokes the #on_extended_data callback when the server sends
572
+ # extended data to the channel. This will reduce the available window
573
+ # size on the local end. The callback is invoked with the channel,
574
+ # type, and data.
575
+ def do_extended_data(type, data)
576
+ update_local_window_size(data.length)
577
+ @on_extended_data.call(self, type, data) if @on_extended_data
578
+ end
579
+
580
+ # Invokes the #on_eof callback when the server indicates that no
581
+ # further data is forthcoming. The callback is invoked with the channel
582
+ # as the argument.
583
+ def do_eof
584
+ @on_eof.call(self) if @on_eof
585
+ end
586
+
587
+ # Invokes the #on_close callback when the server closes a channel.
588
+ # The channel is the only argument.
589
+ def do_close
590
+ @on_close.call(self) if @on_close
591
+ end
592
+
593
+ # Invokes the next pending request callback with +false+ as the second
594
+ # argument.
595
+ def do_failure
596
+ if callback = pending_requests.shift
597
+ callback.call(self, false)
598
+ else
599
+ error { "channel failure recieved with no pending request to handle it (bug?)" }
600
+ end
601
+ end
602
+
603
+ # Invokes the next pending request callback with +true+ as the second
604
+ # argument.
605
+ def do_success
606
+ if callback = pending_requests.shift
607
+ callback.call(self, true)
608
+ else
609
+ error { "channel success recieved with no pending request to handle it (bug?)" }
610
+ end
611
+ end
612
+
613
+ private
614
+
615
+ # Updates the local window size by the given amount. If the window
616
+ # size drops to less than half of the local maximum (an arbitrary
617
+ # threshold), a CHANNEL_WINDOW_ADJUST message will be sent to the
618
+ # server telling it that the window size has grown.
619
+ def update_local_window_size(size)
620
+ @local_window_size -= size
621
+ if local_window_size < local_maximum_window_size/2
622
+ connection.send_message(Buffer.from(:byte, CHANNEL_WINDOW_ADJUST,
623
+ :long, remote_id, :long, 0x20000))
624
+ @local_window_size += 0x20000
625
+ @local_maximum_window_size += 0x20000
626
+ end
627
+ end
628
+ end
629
+
630
+ end; end; end