zerg_support 0.0.5 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ v0.0.8. Extension emulation works in Windows without a build environment.
2
+
3
+ v0.0.7. Windows support.
4
+
5
+ v0.0.6. Event Machine protocols for talking to zergling daemons.
6
+
1
7
  v0.0.5. Added forgotten spawn.rb to Manifest.
2
8
 
3
9
  v0.0.4. Polished implementation and tests for process spawning.
data/Manifest CHANGED
@@ -1,5 +1,9 @@
1
1
  CHANGELOG
2
+ lib/zerg_support/event_machine/connection_mocks.rb
3
+ lib/zerg_support/event_machine/frame_protocol.rb
4
+ lib/zerg_support/event_machine/object_protocol.rb
2
5
  lib/zerg_support/gems.rb
6
+ lib/zerg_support/open_ssh.rb
3
7
  lib/zerg_support/process.rb
4
8
  lib/zerg_support/spawn.rb
5
9
  lib/zerg_support.rb
@@ -7,6 +11,12 @@ LICENSE
7
11
  Manifest
8
12
  Rakefile
9
13
  README
14
+ RUBYFORGE
15
+ test/fork_tree.rb
16
+ test/test_connection_mocks.rb
17
+ test/test_frame_protocol.rb
10
18
  test/test_gems.rb
19
+ test/test_object_protocol.rb
20
+ test/test_open_ssh.rb
11
21
  test/test_process.rb
12
22
  test/test_spawn.rb
data/README CHANGED
@@ -1,61 +0,0 @@
1
- INSTALL
2
- -
3
-
4
- on server and local machine:
5
-
6
- `gem sources -a http://gems.github.com`
7
-
8
- `sudo gem install coderrr-rtunnel`
9
-
10
- If you don't have root access on server, you can use either the rtunnel_server_linux binary (only works with linux), or extract the .tar.gz and use `rtunnel_server.rb` (all function the same)
11
-
12
- USAGE
13
- -
14
-
15
- on server (myserver.com):
16
-
17
- `rtunnel_server`
18
-
19
- on your local machine:
20
-
21
- `rtunnel_client -c myserver.com -f 4000 -t 3000`
22
-
23
- This would reverse tunnel myserver.com:4000 to localhost:3000 so that if you had a web server running at port 3000 on your local machine, anyone on the internet could access it by going to http://myserver.com:4000
24
-
25
- **News**
26
-
27
- * 0.3.6 released, new protocol
28
- * created gem for easier installation
29
- * 0.2.1 released, minor bugfix, cmdline options change
30
- * 0.2.0 released, much simpler
31
- * 0.1.2 released
32
- * Created rtunnel_server binary for linux so you don't need Ruby installed on the host you want to reverse tunnel from
33
- * 0.1.1 released
34
- * Added default control port of 19050, no longer have to specify this on client or server unless you care to change it
35
-
36
- RTunnel?
37
- -
38
-
39
- This client/server allow you to reverse tunnel traffic. Reverse tunneling is useful if you want to run a server behind a NAT and you do not have the ability use port forwarding. The specific reason I created this program was to reduce the pain of Facebook App development on a crappy internet connection that drops often. ssh -R was not cutting it.
40
-
41
- **How does reverse tunneling work?**
42
-
43
- * tunnel\_client makes connection to tunnel\_server (through NAT)
44
- * tunnel_server listens on port X
45
- * internet_user connects to port X on tunnel server
46
- * tunnel\_server uses existing connection to tunnel internet user's request back to tunnel\_client
47
- * tunnel_client connects to local server on port Y
48
- * tunnel_client tunnels internet users connection through to local server
49
-
50
- or:
51
-
52
- * establish connection: tunnel\_client --NAT--> tunnel\_server
53
- * reverse tunnel: internet\_user -> tunnel_server --(NAT)--> tunnel\_client -> server\_running\_behind\_nat
54
-
55
- **How is this different than normal tunneling?**
56
-
57
- With tunneling, usually your connections are made in the same direction you create the tunnel connection. With reverse tunneling, you tunnel your connections the opposite direction of which you made the tunnel connection. So you initiate the tunnel with A -> B, but connections are tunneled from B -> A.
58
-
59
- **Why not just use ssh -R?**
60
-
61
- The same thing can be achieved with ssh -R, why not just use it? A lot of ssh servers don't have the GatewayPorts sshd option set up to allow you to reverse tunnel. If you are not in control of the server and it is not setup correctly then you are SOL. RTunnel does not require you are in control of the server. ssh -R has other annoyances. When your connection drops and you try to re-initiate the reverse tunnel sometimes you get an address already in use error because the old tunnel process is still laying around. This requires you to kill the existing sshd process. RTunnel does not have this problem.
data/RUBYFORGE ADDED
@@ -0,0 +1,40 @@
1
+ Quickstart for Rubyforge:
2
+
3
+ 1) Get the code
4
+ git clone git@github.com:costan/zerg_support.git
5
+
6
+ 2) Install the rubyforge gem
7
+ gem install rubyforge
8
+
9
+ 3) Save your rubyforge.org login information
10
+ rubyforge setup
11
+
12
+ 4) Get a login cookie
13
+ rubyforge login
14
+
15
+ 5) Get project configuration from rubyforge
16
+ rubyforge config zerglings
17
+
18
+ 6) Create a package to release under
19
+ rubyforge create_package zerglings zerg_support
20
+
21
+ 7) Install the echoe gem (required for building this gem)
22
+ gem install echoe
23
+
24
+ 8) Release the gem (finally!)
25
+ rake release
26
+
27
+ Releasing a new gemspec to Github
28
+
29
+ 1) Build the gem
30
+ rake package
31
+
32
+ 2) Copy the spec
33
+ cp pkg/zerg_support-*/zerg_support.gemspec .
34
+
35
+ 3) Commit the spec
36
+ git add zerg_support.gemspec
37
+ git commit -m "New gemspec, for Github distribution."
38
+
39
+ 4) Push to Github
40
+ git push
data/Rakefile CHANGED
@@ -3,18 +3,21 @@ gem 'echoe'
3
3
  require 'echoe'
4
4
 
5
5
  Echoe.new('zerg_support') do |p|
6
- p.project = 'rails-pwnage' # rubyforge project
6
+ p.project = 'zerglings' # rubyforge project
7
7
 
8
8
  p.author = 'Victor Costan'
9
9
  p.email = 'victor@zergling.net'
10
- p.summary = 'Support libraries used by the Zerg system.'
10
+ p.summary = 'Support libraries used by Zergling.Net deployment code.'
11
11
  p.url = 'http://www.zergling.net'
12
12
 
13
- p.need_tar_gz = true
14
- p.need_zip = true
13
+ p.need_tar_gz = !Platform.windows?
14
+ p.need_zip = !Platform.windows?
15
15
  p.rdoc_pattern = /^(lib|bin|tasks|ext)|^BUILD|^README|^CHANGELOG|^TODO|^LICENSE|^COPYING$/
16
16
 
17
- p.development_dependencies = ["flexmock >=0.8.3"]
17
+ p.development_dependencies = ["echoe >=3.0.2",
18
+ "event_machine >=0.12.2",
19
+ "flexmock >=0.8.3",
20
+ ]
18
21
  end
19
22
 
20
23
  if $0 == __FILE__
@@ -0,0 +1,46 @@
1
+ # Mocks the sending end of an EventMachine connection.
2
+ # The sent data is concatenated in a string available by calling #string.
3
+ class Zerg::Support::EventMachine::SendMock
4
+ attr_reader :string
5
+
6
+ def initialize
7
+ @string = ''
8
+ end
9
+
10
+ def send_data(data)
11
+ @string << data
12
+ end
13
+ end
14
+
15
+ # Mocks the receiving end of an EventMachine connection.
16
+ # The data to be received is passed as an array of strings to the constructor.
17
+ # Calling #replay mocks receiving the data.
18
+ class Zerg::Support::EventMachine::ReceiveMock
19
+ attr_accessor :strings
20
+ attr_accessor :objects
21
+
22
+ def initialize(strings = [''])
23
+ @strings = strings
24
+ @objects = []
25
+ end
26
+
27
+ # Simulates receiving all the given strings as data from Event Machine.
28
+ def replay
29
+ @strings.each { |str| receive_data str }
30
+ self
31
+ end
32
+
33
+ #:nodoc:
34
+ def receive__object(object)
35
+ @objects << object
36
+ end
37
+
38
+ # Declares the name of the object to be received. For instance, a frame
39
+ # protocol would use :frame for name. This generates a receive_frame method,
40
+ # and a frames accessor.
41
+ def self.object_name(name)
42
+ alias_method "receive_#{name}".to_sym, :receive__object
43
+ return if name == :object
44
+ alias_method "#{name}s".to_sym, :objects
45
+ end
46
+ end
@@ -0,0 +1,68 @@
1
+ #:nodoc: namespace
2
+ module Zerg::Support::EventMachine
3
+
4
+ # Event Machine protocol for sending and receiving discrete-sized frames
5
+ module FrameProtocol
6
+ #:nodoc: This is called by Event Machine when TCP stream data is available.
7
+ def receive_data(data)
8
+ @frame_protocol_varsize ||= ''
9
+
10
+ i = 0
11
+ loop do
12
+ while @frame_protocol_buffer.nil? and i < data.size
13
+ @frame_protocol_varsize << data[i]
14
+ if (data[i] & 0x80) == 0
15
+ @frame_protocol_bytes_left =
16
+ FrameProtocol.decode_natural @frame_protocol_varsize
17
+ @frame_protocol_buffer = ''
18
+ end
19
+ i += 1
20
+ end
21
+
22
+ return if @frame_protocol_buffer.nil?
23
+ break if @frame_protocol_bytes_left > data.size - i
24
+
25
+ receive_frame @frame_protocol_buffer + data[i, @frame_protocol_bytes_left]
26
+ @frame_protocol_varsize, @frame_protocol_buffer = '', nil
27
+ i += @frame_protocol_bytes_left
28
+ end
29
+
30
+ @frame_protocol_buffer << data[i..-1]
31
+ @frame_protocol_bytes_left -= data.size-i
32
+ end
33
+
34
+ # Override to process incoming frames.
35
+ def receive_frame(frame_data); end
36
+
37
+ # Sends a frame via the underlying Event Machine TCP stream.
38
+ def send_frame(frame_data)
39
+ encoded_length = FrameProtocol.encode_natural(frame_data.length)
40
+ send_data encoded_length + frame_data
41
+ end
42
+
43
+ #:nodoc: Encodes a natural (non-negative) integer into a string.
44
+ def self.encode_natural(number)
45
+ string = ''
46
+ loop do
47
+ number, byte = number.divmod(0x80)
48
+ string << (byte | ((number > 0) ? 0x80 : 0x00))
49
+ break if number == 0
50
+ end
51
+ string
52
+ end
53
+
54
+ #:nodoc: Decodes a natural (non-negative) integer from a string.
55
+ def self.decode_natural(string)
56
+ number = 0
57
+ multiplier = 1
58
+ string.each_byte do |byte|
59
+ more, number_bits = byte.divmod 0x80
60
+ number += number_bits * multiplier
61
+ break if more == 0
62
+ multiplier *= 0x80
63
+ end
64
+ return number
65
+ end
66
+ end
67
+
68
+ end # namespace Zerg::Support::EventMachine
@@ -0,0 +1,24 @@
1
+ require 'yaml'
2
+
3
+ #:nodoc: namespace
4
+ module Zerg::Support::EventMachine
5
+
6
+ # Event Machine protocol for sending serializable objects.
7
+ module ObjectProtocol
8
+ include FrameProtocol
9
+
10
+ # Send a serialized object.
11
+ def send_object(object)
12
+ send_frame YAML.dump(object)
13
+ end
14
+
15
+ #:nodoc: Processes an incoming frame and de-serializes the object in it.
16
+ def receive_frame(frame_data)
17
+ receive_object YAML.load(frame_data)
18
+ end
19
+
20
+ # Override to process incoming objects.
21
+ def receive_object(object); end
22
+ end
23
+
24
+ end # namespace Zerg::Support::EventMachine
@@ -2,23 +2,42 @@
2
2
  module Zerg::Support::Gems
3
3
  # called by ensure_on_path for Windows systems
4
4
  def self.ensure_on_windows_path(bin_file)
5
- # TODO(victor): test this; it's likely that a .bat will be needed instead
6
- path = "/windows/#{File.basename bin_file}"
7
- return if File.exists? path
8
- FileUtils.ln_s(bin_file, path, :force)
5
+ bat_file = File.expand_path(File.join(ENV["WINDIR"],
6
+ File.basename(bin_file) + ".bat"))
7
+ begin
8
+ File.open(bat_file, 'w') do |f|
9
+ f.write <<END_BATCH
10
+ @ECHO OFF
11
+ IF NOT "%~f0" == "~f0" GOTO :WinNT
12
+ @"ruby.exe" "#{File.expand_path(bin_file)}" %1 %2 %3 %4 %5 %6 %7 %8 %9
13
+ GOTO :EOF
14
+ :WinNT
15
+ @"ruby.exe" "#{File.expand_path(bin_file)}" %*
16
+ END_BATCH
17
+ end
18
+ #rescue
19
+ # if anything goes wrong we probably don't have permissions (hi Vista?)
20
+ end
9
21
  end
10
22
 
11
23
  # called by ensure_on_path for UNIX systems
12
24
  def self.ensure_on_unix_path(bin_file)
13
25
  path = "/usr/bin/#{File.basename bin_file}"
14
- return if File.exists? path
15
- # using a link so the gem can be updated and the link still works
16
- FileUtils.ln_s(bin_file, path, :force)
26
+ begin
27
+ # using a link so the gem can be updated and the link still works
28
+ FileUtils.ln_s(bin_file, path, :force)
29
+ rescue
30
+ # if anything goes wrong we probably don't have permissions
31
+ # oh well at least we tried
32
+ end
17
33
  end
18
34
 
19
35
  # ensures that bin_file can be invoked from a shell
20
36
  def self.ensure_on_path(bin_script)
21
- bin_file = File.expand_path(__FILE__ + '/../../../bin/rpwn')
37
+ caller_trace = Kernel.caller.first
38
+ caller_match = /^(.*)\:\d+\:in /.match(caller_trace) ||
39
+ /^(.*)\:\d+$/.match(caller_trace)
40
+ bin_file = File.expand_path caller_match[1] + '/../../../bin/' + bin_script
22
41
  # this is a cheat to get the binary in the right place on stubborn Debians
23
42
  if RUBY_PLATFORM =~ /win/ and RUBY_PLATFORM !~ /darwin/
24
43
  ensure_on_windows_path bin_file
@@ -30,7 +49,12 @@ module Zerg::Support::Gems
30
49
  # tricks rubygems into believeing that the extension compiled and worked out
31
50
  def self.emulate_extension_install(extension_name)
32
51
  File.open('Makefile', 'w') { |f| f.write "all:\n\ninstall:\n\n" }
52
+ File.open('make', 'w') do |f|
53
+ f.write '#!/bin/sh'
54
+ f.chmod f.stat.mode | 0111
55
+ end
33
56
  File.open(extension_name + '.so', 'w') {}
34
57
  File.open(extension_name + '.dll', 'w') {}
58
+ File.open('nmake.bat', 'w') { |f| }
35
59
  end
36
- end
60
+ end
@@ -0,0 +1,104 @@
1
+ require 'base64'
2
+ require 'openssl'
3
+
4
+ # Tools for managing openssh cryptographic material
5
+ module Zerg::Support::OpenSSH
6
+ # Extracts the keys from a file of known_hosts format.
7
+ def self.known_hosts_keys(io)
8
+ io.each_line do |line|
9
+
10
+ end
11
+ end
12
+
13
+ # The components in a openssh .pub / known_host RSA public key.
14
+ RSA_COMPONENTS = ['ssh-rsa', :e, :n]
15
+ # The components in a openssh .pub / known_host DSA public key.
16
+ DSA_COMPONENTS = ['ssh-dss', :p, :q, :g, :pub_key]
17
+
18
+ # Encodes a key's public part in the format found in .pub & known_hosts files.
19
+ def self.encode_pubkey(key)
20
+ case key
21
+ when OpenSSL::PKey::RSA
22
+ components = RSA_COMPONENTS
23
+ when OpenSSL::PKey::DSA
24
+ components = DSA_COMPONENTS
25
+ else
26
+ raise "Unsupported key type #{key.class.name}"
27
+ end
28
+ components.map! { |c| c.kind_of?(Symbol) ? encode_mpi(key.send(c)) : c }
29
+ # ruby tries to be helpful and adds new lines every 60 bytes :(
30
+ [pack_pubkey_components(components)].pack('m').gsub("\n", '')
31
+ end
32
+
33
+ # Decodes an openssh public key from the format of .pub & known_hosts files.
34
+ def self.decode_pubkey(string)
35
+ components = unpack_pubkey_components Base64.decode64(string)
36
+ case components.first
37
+ when RSA_COMPONENTS.first
38
+ ops = RSA_COMPONENTS.zip components
39
+ key = OpenSSL::PKey::RSA.new
40
+ when DSA_COMPONENTS.first
41
+ ops = DSA_COMPONENTS.zip components
42
+ key = OpenSSL::PKey::DSA.new
43
+ else
44
+ raise "Unsupported key type #{components.first}"
45
+ end
46
+ ops.each do |o|
47
+ next unless o.first.kind_of? Symbol
48
+ key.send "#{o.first}=", decode_mpi(o.last)
49
+ end
50
+ return key
51
+ end
52
+
53
+ # Loads a serialized key from an IO instance (File, StringIO).
54
+ def self.load_key(io)
55
+ key_from_string io.read
56
+ end
57
+
58
+ # Reads a serialized key from a string.
59
+ def self.key_from_string(serialized_key)
60
+ header = first_line serialized_key
61
+ if header.index 'RSA'
62
+ OpenSSL::PKey::RSA.new serialized_key
63
+ elsif header.index 'DSA'
64
+ OpenSSL::PKey::DSA.new serialized_key
65
+ else
66
+ raise 'Unknown key type'
67
+ end
68
+ end
69
+
70
+ # Extracts the first line of a string.
71
+ def self.first_line(string)
72
+ string[0, string.index(/\r|\n/) || string.len]
73
+ end
74
+
75
+ # Unpacks the string components in an openssh-encoded pubkey.
76
+ def self.unpack_pubkey_components(str)
77
+ cs = []
78
+ i = 0
79
+ while i < str.length
80
+ len = str[i, 4].unpack('N').first
81
+ cs << str[i + 4, len]
82
+ i += 4 + len
83
+ end
84
+ return cs
85
+ end
86
+
87
+ # Packs string components into an openssh-encoded pubkey.
88
+ def self.pack_pubkey_components(strings)
89
+ (strings.map { |s| [s.length].pack('N') }).zip(strings).flatten.join
90
+ end
91
+
92
+ # Decodes an openssh-mpi-encoded integer.
93
+ def self.decode_mpi(mpi_str)
94
+ mpi_str.unpack('C*').inject(0) { |acc, c| (acc << 8) | c }
95
+ end
96
+
97
+ # Encodes an openssh-mpi-encoded integer.
98
+ def self.encode_mpi(n)
99
+ chars, n = [], n.to_i
100
+ chars << (n & 0xff) and n >>= 8 while n != 0
101
+ chars << 0 if chars.empty? or chars.last >= 0x80
102
+ chars.reverse.pack('C*')
103
+ end
104
+ end
@@ -4,25 +4,52 @@ require 'time'
4
4
  module Zerg::Support::Process
5
5
  @@no_multiple_pids = false
6
6
 
7
- # Translates process info given by Sys::ProcTable into our own nicer format.
8
- def self.xlate_process_info(low_info)
9
- {
10
- :pid => low_info.pid,
11
- :parent_pid => low_info.ppid,
12
- :real_uid => low_info.ruid || -1,
13
- :real_gid => low_info.rgid || -1,
14
- :start_time => low_info.start,
15
- :nice => low_info.nice || 0,
16
- :priority => low_info.priority || 0,
17
- :syscall_priority => low_info.user_priority || 0,
18
- :resident_size => low_info.id_rss || 0,
19
- :code_size => low_info.ix_rss || 0,
20
- :virtual_size => low_info.is_rss || 0,
21
- :percent_cpu => low_info.pctcpu,
22
- :percent_ram => low_info.pctmem,
23
- :state => low_info.state,
24
- :command_line => low_info.cmdline
25
- }
7
+ unless RUBY_PLATFORM =~ /win/ and RUBY_PLATFORM !~ /darwin/
8
+ # Translates process info given by Sys::ProcTable into our own nicer format.
9
+ def self.xlate_process_info(low_info)
10
+ {
11
+ :pid => low_info.pid,
12
+ :parent_pid => low_info.ppid,
13
+ :real_uid => low_info.ruid || -1,
14
+ :real_gid => low_info.rgid || -1,
15
+ :start_time => low_info.start,
16
+ :nice => low_info.nice || 0,
17
+ :priority => low_info.priority || 0,
18
+ :syscall_priority => low_info.user_priority || 0,
19
+ :resident_size => (low_info.id_rss || 0) * 1024,
20
+ :code_size => (low_info.ix_rss || 0) * 1024,
21
+ :virtual_size => (low_info.is_rss || 0) * 1024,
22
+ :percent_cpu => low_info.pctcpu,
23
+ :percent_ram => low_info.pctmem,
24
+ :state => low_info.state,
25
+ :command_line => low_info.cmdline
26
+ }
27
+ end
28
+
29
+ else
30
+ #:nodoc:
31
+ def self.xlate_process_info(low_info)
32
+ state = low_info.execution_state.to_s
33
+ state << 's' if low_info.session_id == 0
34
+
35
+ {
36
+ :pid => low_info.pid,
37
+ :parent_pid => low_info.ppid,
38
+ :real_uid => -1,
39
+ :real_gid => -1,
40
+ :start_time => low_info.creation_date,
41
+ :nice => 0,
42
+ :priority => low_info.priority || 0,
43
+ :syscall_priority => 0,
44
+ :resident_size => low_info.working_set_size || 0,
45
+ :code_size => 0,
46
+ :virtual_size => low_info.virtual_size || 0,
47
+ :percent_cpu => 0,
48
+ :percent_ram => 0,
49
+ :state => state,
50
+ :command_line => low_info.cmdline || low_info.comm || ''
51
+ }
52
+ end
26
53
  end
27
54
 
28
55
  # Collects information about a single process. Returns nil
@@ -81,13 +108,26 @@ module Zerg::Support::Process
81
108
  return proc_queue
82
109
  end
83
110
 
111
+
112
+ if RUBY_PLATFORM =~ /win/ and RUBY_PLATFORM !~ /darwin/
113
+ #:nodoc: Wrapper around Process.kill that works on all platforms.
114
+ def self.kill_primitive(pid, force = false)
115
+ Process.kill(force ? 9 : 4, pid)
116
+ end
117
+ else
118
+ #:nodoc: Wrapper around Process.kill that works on all platforms.
119
+ def self.kill_primitive(pid, force = false)
120
+ Process.kill(force ? 'KILL' : 'TERM', pid)
121
+ end
122
+ end
123
+
84
124
  # Kills the process with the given pid.
85
125
  def self.kill(pid)
86
126
  begin
87
- Process.kill "TERM", pid
127
+ self.kill_primitive pid, false
88
128
  Thread.new(pid) do |victim_pid|
89
129
  Kernel.sleep 0.2
90
- Process.kill "KILL", victim_pid
130
+ self.kill_primitive pid, true
91
131
  end
92
132
  rescue
93
133
  # we probably don't have the right to kill the process
@@ -107,7 +147,9 @@ end
107
147
  ## Backend for process information listing
108
148
 
109
149
  begin
110
- raise 'Use ps' unless RUBY_PLAYFORM =~ /win/ and RUBY_PLATFORM !~ /darwin/
150
+ raise 'Use ps' unless RUBY_PLATFORM =~ /win/ and RUBY_PLATFORM !~ /darwin/
151
+
152
+ require 'time'
111
153
  require 'sys/proctable'
112
154
  rescue Exception
113
155
  # Emulate the sys-proctable gem using the ps command.
@@ -171,4 +213,4 @@ rescue Exception
171
213
  return pids.length == 1 ? retval.first : retval
172
214
  end
173
215
  end
174
- end
216
+ end