i2p 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/AUTHORS +1 -0
- data/CONTRIBUTORS +0 -0
- data/README +155 -0
- data/UNLICENSE +24 -0
- data/VERSION +1 -0
- data/lib/i2p.rb +177 -0
- data/lib/i2p/bob.rb +27 -0
- data/lib/i2p/bob/client.rb +489 -0
- data/lib/i2p/bob/tunnel.rb +303 -0
- data/lib/i2p/data/certificate.rb +75 -0
- data/lib/i2p/data/destination.rb +68 -0
- data/lib/i2p/data/key.rb +40 -0
- data/lib/i2p/data/key_pair.rb +65 -0
- data/lib/i2p/data/private_key.rb +14 -0
- data/lib/i2p/data/public_key.rb +14 -0
- data/lib/i2p/data/signing_private_key.rb +10 -0
- data/lib/i2p/data/signing_public_key.rb +10 -0
- data/lib/i2p/data/structure.rb +84 -0
- data/lib/i2p/hosts.rb +207 -0
- data/lib/i2p/sam.rb +40 -0
- data/lib/i2p/sam/client.rb +226 -0
- data/lib/i2p/sdk.jar +0 -0
- data/lib/i2p/sdk.rb +111 -0
- data/lib/i2p/streaming.jar +0 -0
- data/lib/i2p/streaming.rb +10 -0
- data/lib/i2p/version.rb +22 -0
- metadata +116 -0
data/AUTHORS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
* Arto Bendiken <arto.bendiken@gmail.com>
|
data/CONTRIBUTORS
ADDED
File without changes
|
data/README
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
I2P.rb: Anonymous Networking for Ruby
|
2
|
+
=====================================
|
3
|
+
|
4
|
+
This is a Ruby library for interacting with the [I2P][] anonymity network.
|
5
|
+
|
6
|
+
* <http://github.com/bendiken/i2p-ruby>
|
7
|
+
|
8
|
+
Features
|
9
|
+
--------
|
10
|
+
|
11
|
+
* Supports checking whether I2P is installed in the user's current `PATH`
|
12
|
+
and whether the I2P router is currently running.
|
13
|
+
* Supports starting, restarting and stopping the I2P router daemon.
|
14
|
+
* Implements the [I2P Basic Open Bridge (BOB)][BOB] protocol.
|
15
|
+
* Implements the basics of the [I2P Simple Anonymous Messaging (SAM)][SAM]
|
16
|
+
protocol.
|
17
|
+
* Supports I2P name resolution using both `hosts.txt` as well as SAM.
|
18
|
+
* Compatible with Ruby 1.8.7+, Ruby 1.9.x, and JRuby 1.4/1.5.
|
19
|
+
* Bundles the I2P 0.8 [SDK][] and [Streaming Library][Streaming] for use
|
20
|
+
with [JRuby][],
|
21
|
+
|
22
|
+
Examples
|
23
|
+
--------
|
24
|
+
|
25
|
+
require 'rubygems'
|
26
|
+
require 'i2p'
|
27
|
+
|
28
|
+
### Checking whether an I2P router is running locally
|
29
|
+
|
30
|
+
I2P.available? #=> true, if the I2P router is installed
|
31
|
+
I2P.running? #=> true, if the I2P router is running
|
32
|
+
|
33
|
+
### Starting and stopping the local I2P router daemon
|
34
|
+
|
35
|
+
I2P.start! #=> executes `i2prouter start`
|
36
|
+
I2P.restart! #=> executes `i2prouter restart`
|
37
|
+
I2P.stop! #=> executes `i2prouter stop`
|
38
|
+
|
39
|
+
### Looking up the public key for an I2P name from hosts.txt
|
40
|
+
|
41
|
+
puts I2P::Hosts["forum.i2p"].to_base64
|
42
|
+
|
43
|
+
### Looking up the public key for an I2P name using SAM
|
44
|
+
|
45
|
+
I2P::SAM::Client.open(:port => 7656) do |sam|
|
46
|
+
puts sam.lookup_name("forum.i2p").to_base64
|
47
|
+
end
|
48
|
+
|
49
|
+
### Generating a new key pair and I2P destination using SAM
|
50
|
+
|
51
|
+
I2P::SAM::Client.open(:port => 7656) do |sam|
|
52
|
+
key_pair = sam.generate_destination
|
53
|
+
puts key_pair.destination.to_base64
|
54
|
+
end
|
55
|
+
|
56
|
+
### Creating an inproxy tunnel to an eepsite using BOB
|
57
|
+
|
58
|
+
I2P::BOB::Tunnel.start(:inport => 12345) do |tunnel|
|
59
|
+
sleep 0.1 until tunnel.running?
|
60
|
+
|
61
|
+
TCPSocket.open("127.0.0.1", 12345) do |socket|
|
62
|
+
socket.puts "bob.i2p" # the I2P destination
|
63
|
+
|
64
|
+
socket.write "HEAD / HTTP/1.1\r\n\r\n"
|
65
|
+
socket.flush
|
66
|
+
until (line = socket.readline).chomp.empty?
|
67
|
+
puts line
|
68
|
+
end
|
69
|
+
socket.close
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
### Creating an outproxy tunnel to your SSH daemon using BOB
|
74
|
+
|
75
|
+
tunnel = I2P::BOB::Tunnel.start({
|
76
|
+
:nickname => :myssh,
|
77
|
+
:outhost => "127.0.0.1",
|
78
|
+
:outport => 22, # SSH port
|
79
|
+
:quiet => true,
|
80
|
+
})
|
81
|
+
puts tunnel.destination.to_base64
|
82
|
+
|
83
|
+
### Using the I2P SDK and Streaming Library directly from JRuby
|
84
|
+
|
85
|
+
I2P.rb bundles the public-domain [I2P SDK][SDK] (`i2p/sdk.jar`) and
|
86
|
+
[Streaming Library][Streaming] (`i2p/streaming.jar`) archives, which means
|
87
|
+
that to [script][JRuby howto] the I2P Java client implementation from
|
88
|
+
[JRuby][], you need only require these two files as follows:
|
89
|
+
|
90
|
+
require 'i2p/sdk.jar'
|
91
|
+
require 'i2p/streaming.jar'
|
92
|
+
|
93
|
+
Documentation
|
94
|
+
-------------
|
95
|
+
|
96
|
+
* <http://cypherpunk.rubyforge.org/i2p/>
|
97
|
+
|
98
|
+
Dependencies
|
99
|
+
------------
|
100
|
+
|
101
|
+
* [Ruby](http://ruby-lang.org/) (>= 1.8.7) or (>= 1.8.1 with [Backports][])
|
102
|
+
* [I2P](http://www.i2p2.de/download.html) (>= 0.8)
|
103
|
+
|
104
|
+
Installation
|
105
|
+
------------
|
106
|
+
|
107
|
+
The recommended installation method is via [RubyGems](http://rubygems.org/).
|
108
|
+
To install the latest official release of I2P.rb, do:
|
109
|
+
|
110
|
+
% [sudo] gem install i2p # Ruby 1.8.7+ or 1.9.x
|
111
|
+
% [sudo] gem install backports i2p # Ruby 1.8.1+
|
112
|
+
|
113
|
+
Environment
|
114
|
+
-----------
|
115
|
+
|
116
|
+
The following are the default values for environment variables that let
|
117
|
+
you customize I2P.rb's implicit configuration:
|
118
|
+
|
119
|
+
$ export I2P_PATH=$PATH
|
120
|
+
$ export I2P_BOB_HOST=127.0.0.1
|
121
|
+
$ export I2P_BOB_PORT=2827
|
122
|
+
$ export I2P_SAM_HOST=127.0.0.1
|
123
|
+
$ export I2P_SAM_PORT=7656
|
124
|
+
|
125
|
+
Download
|
126
|
+
--------
|
127
|
+
|
128
|
+
To get a local working copy of the development repository, do:
|
129
|
+
|
130
|
+
% git clone git://github.com/bendiken/i2p-ruby.git
|
131
|
+
|
132
|
+
Alternatively, you can download the latest development version as a tarball
|
133
|
+
as follows:
|
134
|
+
|
135
|
+
% wget http://github.com/bendiken/i2p-ruby/tarball/master
|
136
|
+
|
137
|
+
Author
|
138
|
+
------
|
139
|
+
|
140
|
+
* [Arto Bendiken](mailto:arto.bendiken@gmail.com) - <http://ar.to/>
|
141
|
+
|
142
|
+
License
|
143
|
+
-------
|
144
|
+
|
145
|
+
I2P.rb is free and unencumbered public domain software. For more
|
146
|
+
information, see <http://unlicense.org/> or the accompanying UNLICENSE file.
|
147
|
+
|
148
|
+
[I2P]: http://www.i2p2.de/
|
149
|
+
[SDK]: http://www.i2p2.de/package-client.html
|
150
|
+
[Streaming]: http://www.i2p2.de/package-streaming.html
|
151
|
+
[SAM]: http://www.i2p2.de/samv3.html
|
152
|
+
[BOB]: http://bob.i2p.to/bridge.htm
|
153
|
+
[JRuby]: http://jruby.org/
|
154
|
+
[JRuby howto]: http://kenai.com/projects/jruby/pages/CallingJavaFromJRuby
|
155
|
+
[Backports]: http://rubygems.org/gems/backports
|
data/UNLICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
2
|
+
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
4
|
+
distribute this software, either in source code form or as a compiled
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
6
|
+
means.
|
7
|
+
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
9
|
+
of this software dedicate any and all copyright interest in the
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
11
|
+
of the public at large and to the detriment of our heirs and
|
12
|
+
successors. We intend this dedication to be an overt act of
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
14
|
+
software under copyright law.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
For more information, please refer to <http://unlicense.org/>
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.4
|
data/lib/i2p.rb
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'socket'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
if RUBY_VERSION < '1.8.7'
|
6
|
+
# @see http://rubygems.org/gems/backports
|
7
|
+
begin
|
8
|
+
require 'backports/1.8.7'
|
9
|
+
rescue LoadError
|
10
|
+
begin
|
11
|
+
require 'rubygems'
|
12
|
+
require 'backports/1.8.7'
|
13
|
+
rescue LoadError
|
14
|
+
abort "I2P.rb requires Ruby 1.8.7 or the Backports gem (hint: `gem install backports')."
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# @example Checking whether an I2P router is running locally
|
21
|
+
# I2P.available? #=> true, if the I2P router is installed
|
22
|
+
# I2P.running? #=> true, if the I2P router is running
|
23
|
+
#
|
24
|
+
# @example Starting and stopping the local I2P router daemon
|
25
|
+
# I2P.start! #=> executes `i2prouter start`
|
26
|
+
# I2P.restart! #=> executes `i2prouter restart`
|
27
|
+
# I2P.stop! #=> executes `i2prouter stop`
|
28
|
+
#
|
29
|
+
# @see http://www.i2p2.de/download.html
|
30
|
+
module I2P
|
31
|
+
# Name resolution
|
32
|
+
autoload :Hosts, 'i2p/hosts'
|
33
|
+
|
34
|
+
# Data structures
|
35
|
+
autoload :Structure, 'i2p/data/structure'
|
36
|
+
autoload :Certificate, 'i2p/data/certificate'
|
37
|
+
autoload :Key, 'i2p/data/key'
|
38
|
+
autoload :PrivateKey, 'i2p/data/private_key'
|
39
|
+
autoload :SigningPrivateKey, 'i2p/data/signing_private_key'
|
40
|
+
autoload :PublicKey, 'i2p/data/public_key'
|
41
|
+
autoload :SigningPublicKey, 'i2p/data/signing_public_key'
|
42
|
+
autoload :Destination, 'i2p/data/destination'
|
43
|
+
autoload :KeyPair, 'i2p/data/key_pair'
|
44
|
+
|
45
|
+
# Client protocols
|
46
|
+
autoload :BOB, 'i2p/bob'
|
47
|
+
autoload :SAM, 'i2p/sam'
|
48
|
+
|
49
|
+
# Miscellaneous
|
50
|
+
autoload :VERSION, 'i2p/version'
|
51
|
+
|
52
|
+
# The path used to locate the `i2prouter` executable.
|
53
|
+
PATH = (ENV['I2P_PATH'] || ENV['PATH']).split(File::PATH_SEPARATOR) unless defined?(PATH)
|
54
|
+
|
55
|
+
##
|
56
|
+
# Returns `true` if I2P is available, `false` otherwise.
|
57
|
+
#
|
58
|
+
# This attempts to locate the `i2prouter` executable in the user's current
|
59
|
+
# `PATH` environment.
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# I2P.available? #=> true
|
63
|
+
#
|
64
|
+
# @return [Boolean]
|
65
|
+
def self.available?
|
66
|
+
!!program_path
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Returns `true` if the I2P router is running locally, `false` otherwise.
|
71
|
+
#
|
72
|
+
# This first attempts to call `i2prouter status` if the executable can be
|
73
|
+
# located in the user's current `PATH` environment, falling back to
|
74
|
+
# attempting to establish a Simple Anonymous Messaging (SAM) protocol
|
75
|
+
# connection to the standard SAM port 7656 on `localhost`.
|
76
|
+
#
|
77
|
+
# If I2P isn't in the `PATH` and hasn't been configured with SAM enabled,
|
78
|
+
# this will return `false` regardless of whether I2P actually is running
|
79
|
+
# or not.
|
80
|
+
#
|
81
|
+
# @example
|
82
|
+
# I2P.running? #=> false
|
83
|
+
#
|
84
|
+
# @return [Boolean]
|
85
|
+
def self.running?
|
86
|
+
if available?
|
87
|
+
/is running/ === `#{program_path} status`.chomp
|
88
|
+
else
|
89
|
+
begin
|
90
|
+
I2P::SAM::Client.open.disconnect
|
91
|
+
true
|
92
|
+
rescue Errno::ECONNREFUSED
|
93
|
+
false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# Starts the local I2P router daemon.
|
100
|
+
#
|
101
|
+
# Returns the process identifier (PID) if the I2P router daemon was
|
102
|
+
# successfully started, `nil` otherwise.
|
103
|
+
#
|
104
|
+
# This relies on being able to execute `i2prouter start`, which requires
|
105
|
+
# the `i2prouter` executable to be located in the user's current `PATH`
|
106
|
+
# environment.
|
107
|
+
#
|
108
|
+
# @return [Integer]
|
109
|
+
# @since 0.1.1
|
110
|
+
def self.start!
|
111
|
+
if available?
|
112
|
+
`#{program_path} start` unless running?
|
113
|
+
`#{program_path} status` =~ /is running \((\d+)\)/ ? $1.to_i : nil
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# Restarts the local I2P router daemon, starting it in case it wasn't
|
119
|
+
# already running.
|
120
|
+
#
|
121
|
+
# Returns `true` if the I2P router daemon was successfully restarted,
|
122
|
+
# `false` otherwise.
|
123
|
+
#
|
124
|
+
# This relies on being able to execute `i2prouter restart`, which requires
|
125
|
+
# the `i2prouter` executable to be located in the user's current `PATH`
|
126
|
+
# environment.
|
127
|
+
#
|
128
|
+
# @return [Boolean]
|
129
|
+
# @since 0.1.1
|
130
|
+
def self.restart!
|
131
|
+
if available?
|
132
|
+
/Starting I2P Service/ === `#{program_path} restart`
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
##
|
137
|
+
# Stops the local I2P router daemon.
|
138
|
+
#
|
139
|
+
# Returns `true` if the I2P router daemon was successfully shut down,
|
140
|
+
# `false` otherwise.
|
141
|
+
#
|
142
|
+
# This relies on being able to execute `i2prouter stop`, which requires
|
143
|
+
# the `i2prouter` executable to be located in the user's current `PATH`
|
144
|
+
# environment.
|
145
|
+
#
|
146
|
+
# @return [Boolean]
|
147
|
+
# @since 0.1.1
|
148
|
+
def self.stop!
|
149
|
+
if available?
|
150
|
+
/Stopped I2P Service/ === `#{program_path} stop`
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
##
|
155
|
+
# Returns the path to the `i2prouter` executable.
|
156
|
+
#
|
157
|
+
# Returns `nil` if the program could not be located in any of the
|
158
|
+
# directories denoted by the user's current `I2P_PATH` or `PATH`
|
159
|
+
# environment variables.
|
160
|
+
#
|
161
|
+
# @example
|
162
|
+
# I2P.program_path #=> "/opt/local/bin/i2prouter"
|
163
|
+
#
|
164
|
+
# @param [String, #to_s] program_name
|
165
|
+
# @return [Pathname]
|
166
|
+
def self.program_path(program_name = :i2prouter)
|
167
|
+
program_name = program_name.to_s
|
168
|
+
@program_paths ||= {}
|
169
|
+
@program_paths[program_name] ||= begin
|
170
|
+
PATH.find do |dir|
|
171
|
+
if File.executable?(file = File.join(dir, program_name))
|
172
|
+
break Pathname(file)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
data/lib/i2p/bob.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module I2P
|
2
|
+
##
|
3
|
+
# **I2P Basic Open Bridge (BOB) protocol.**
|
4
|
+
#
|
5
|
+
# This is an implementation of the BOB protocol, available since I2P
|
6
|
+
# release [0.6.5](http://www.i2p2.de/release-0.6.5.html).
|
7
|
+
#
|
8
|
+
# Note that for security reasons, the BOB application bridge is not
|
9
|
+
# enabled by default in new I2P installations. To use `I2P::BOB`,
|
10
|
+
# you must first manually enable BOB in the router console's
|
11
|
+
# [client configuration](http://localhost:7657/configclients.jsp).
|
12
|
+
#
|
13
|
+
# @see http://www.i2p2.de/applications.html
|
14
|
+
# @see http://bob.i2p.to/bridge.html
|
15
|
+
module BOB
|
16
|
+
PROTOCOL_VERSION = 1
|
17
|
+
DEFAULT_HOST = (ENV['I2P_BOB_HOST'] || '127.0.0.1').to_s
|
18
|
+
DEFAULT_PORT = (ENV['I2P_BOB_PORT'] || 2827).to_i
|
19
|
+
|
20
|
+
autoload :Client, 'i2p/bob/client'
|
21
|
+
autoload :Tunnel, 'i2p/bob/tunnel'
|
22
|
+
|
23
|
+
##
|
24
|
+
# **I2P Basic Open Bridge (BOB) protocol error conditions.**
|
25
|
+
class Error < StandardError; end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,489 @@
|
|
1
|
+
module I2P; module BOB
|
2
|
+
##
|
3
|
+
# **I2P Basic Open Bridge (BOB) client.**
|
4
|
+
#
|
5
|
+
# @example Connecting to the I2P BOB bridge (1)
|
6
|
+
# bob = I2P::BOB::Client.new(:port => 2827)
|
7
|
+
#
|
8
|
+
# @example Connecting to the I2P BOB bridge (2)
|
9
|
+
# I2P::BOB::Client.open(:port => 2827) do |bob|
|
10
|
+
# ...
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# @example Generating a new destination
|
14
|
+
# I2P::BOB::Client.open(:nickname => :foo) do |bob|
|
15
|
+
# bob.newkeys
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# @example Generating a new key pair
|
19
|
+
# I2P::BOB::Client.open(:nickname => :foo) do |bob|
|
20
|
+
# bob.newkeys
|
21
|
+
# bob.getkeys
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# @see http://www.i2p2.de/applications.html
|
25
|
+
# @see http://bob.i2p.to/bridge.html
|
26
|
+
# @since 0.1.4
|
27
|
+
class Client
|
28
|
+
##
|
29
|
+
# Establishes a connection to the BOB bridge.
|
30
|
+
#
|
31
|
+
# @example Connecting to the default port
|
32
|
+
# bob = I2P::BOB::Client.open
|
33
|
+
#
|
34
|
+
# @example Connecting to the given port
|
35
|
+
# bob = I2P::BOB::Client.open(:port => 2827)
|
36
|
+
#
|
37
|
+
# @param [Hash{Symbol => Object}] options
|
38
|
+
# @option options [String, #to_s] :host (DEFAULT_HOST)
|
39
|
+
# @option options [Integer, #to_i] :port (DEFAULT_PORT)
|
40
|
+
# @yield [client]
|
41
|
+
# @yieldparam [Client] client
|
42
|
+
# @return [void]
|
43
|
+
def self.open(options = {}, &block)
|
44
|
+
client = self.new(options)
|
45
|
+
client.connect
|
46
|
+
|
47
|
+
if options[:nickname]
|
48
|
+
begin
|
49
|
+
client.getnick(options[:nickname])
|
50
|
+
rescue Error => e
|
51
|
+
client.setnick(options[:nickname])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
client.setkeys(options[:keys]) if options[:keys]
|
56
|
+
client.quiet(options[:quiet]) if options[:quiet]
|
57
|
+
client.inhost(options[:inhost]) if options[:inhost]
|
58
|
+
client.inport(options[:inport]) if options[:inport]
|
59
|
+
client.outhost(options[:outhost]) if options[:outhost]
|
60
|
+
client.outport(options[:outport]) if options[:outport]
|
61
|
+
|
62
|
+
unless block_given?
|
63
|
+
client
|
64
|
+
else
|
65
|
+
begin
|
66
|
+
result = case block.arity
|
67
|
+
when 1 then block.call(client)
|
68
|
+
else client.instance_eval(&block)
|
69
|
+
end
|
70
|
+
ensure
|
71
|
+
client.disconnect
|
72
|
+
end
|
73
|
+
result
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Returns the socket connection to the BOB bridge.
|
79
|
+
#
|
80
|
+
# @return [TCPSocket]
|
81
|
+
attr_reader :socket
|
82
|
+
|
83
|
+
##
|
84
|
+
# Returns the host name or IP address of the BOB bridge.
|
85
|
+
#
|
86
|
+
# @return [String]
|
87
|
+
attr_reader :host
|
88
|
+
|
89
|
+
##
|
90
|
+
# Returns the port number of the BOB bridge.
|
91
|
+
#
|
92
|
+
# @return [Integer]
|
93
|
+
attr_reader :port
|
94
|
+
|
95
|
+
##
|
96
|
+
# Initializes a new client instance.
|
97
|
+
#
|
98
|
+
# @param [Hash{Symbol => Object}] options
|
99
|
+
# @option options [String, #to_s] :host (DEFAULT_HOST)
|
100
|
+
# @option options [Integer, #to_i] :port (DEFAULT_PORT)
|
101
|
+
# @yield [client]
|
102
|
+
# @yieldparam [Client] client
|
103
|
+
def initialize(options = {}, &block)
|
104
|
+
@options = options.dup
|
105
|
+
@host = (@options.delete(:host) || DEFAULT_HOST).to_s
|
106
|
+
@port = (@options.delete(:port) || DEFAULT_PORT).to_i
|
107
|
+
|
108
|
+
if block_given?
|
109
|
+
case block.arity
|
110
|
+
when 1 then block.call(self)
|
111
|
+
else instance_eval(&block)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# Returns `true` if a connection to the BOB bridge has been established
|
118
|
+
# and is active.
|
119
|
+
#
|
120
|
+
# @example
|
121
|
+
# bob.connected? #=> true
|
122
|
+
# bob.disconnect
|
123
|
+
# bob.connected? #=> false
|
124
|
+
#
|
125
|
+
# @return [Boolean]
|
126
|
+
def connected?
|
127
|
+
!!@socket
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# Establishes a connection to the BOB bridge.
|
132
|
+
#
|
133
|
+
# If called after the connection has already been established,
|
134
|
+
# disconnects and then reconnects to the bridge.
|
135
|
+
#
|
136
|
+
# @example
|
137
|
+
# bob.connect
|
138
|
+
#
|
139
|
+
# @return [void]
|
140
|
+
def connect
|
141
|
+
disconnect if connected?
|
142
|
+
@socket = TCPSocket.new(@host, @port)
|
143
|
+
@socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
|
144
|
+
read_line # "BOB 00.00.0D"
|
145
|
+
read_line # "OK"
|
146
|
+
self
|
147
|
+
end
|
148
|
+
alias_method :reconnect, :connect
|
149
|
+
|
150
|
+
##
|
151
|
+
# Closes the connection to the BOB bridge.
|
152
|
+
#
|
153
|
+
# If called after the connection has already been closed, does nothing.
|
154
|
+
#
|
155
|
+
# @example
|
156
|
+
# bob.disconnect
|
157
|
+
#
|
158
|
+
# @return [void]
|
159
|
+
def disconnect
|
160
|
+
@socket.close if @socket && !@socket.closed?
|
161
|
+
@socket = nil
|
162
|
+
self
|
163
|
+
end
|
164
|
+
alias_method :close, :disconnect
|
165
|
+
|
166
|
+
##
|
167
|
+
# Closes the connection to the BOB bridge cleanly.
|
168
|
+
#
|
169
|
+
# @example
|
170
|
+
# bob.quit
|
171
|
+
#
|
172
|
+
# @return [void]
|
173
|
+
def quit
|
174
|
+
send_command(:quit)
|
175
|
+
read_response # "Bye!"
|
176
|
+
disconnect
|
177
|
+
end
|
178
|
+
alias_method :quit!, :quit
|
179
|
+
|
180
|
+
##
|
181
|
+
# Verifies a Base64-formatted key pair or destination, returning `true`
|
182
|
+
# for valid input.
|
183
|
+
#
|
184
|
+
# @example
|
185
|
+
# bob.verify("foobar") #=> false
|
186
|
+
# bob.verify(I2P::Hosts["forum.i2p"]) #=> true
|
187
|
+
#
|
188
|
+
# @param [#to_base64, #to_s] data
|
189
|
+
# @return [Boolean]
|
190
|
+
def verify(data)
|
191
|
+
send_command(:verify, data.respond_to?(:to_base64) ? data.to_base64 : data.to_s)
|
192
|
+
read_response rescue false
|
193
|
+
end
|
194
|
+
|
195
|
+
##
|
196
|
+
# Creates a new tunnel with the given nickname.
|
197
|
+
#
|
198
|
+
# @example
|
199
|
+
# bob.setnick(:foo)
|
200
|
+
#
|
201
|
+
# @param [String, #to_s] nickname
|
202
|
+
# @return [void]
|
203
|
+
def setnick(nickname)
|
204
|
+
send_command(:setnick, @options[:nickname] = nickname.to_s)
|
205
|
+
read_response # "Nickname set to #{nickname}"
|
206
|
+
self
|
207
|
+
end
|
208
|
+
alias_method :nickname=, :setnick
|
209
|
+
|
210
|
+
##
|
211
|
+
# Selects an existing tunnel with the given nickname.
|
212
|
+
#
|
213
|
+
# @example
|
214
|
+
# bob.getnick(:foo)
|
215
|
+
#
|
216
|
+
# @param [String, #to_s] nickname
|
217
|
+
# @return [void]
|
218
|
+
def getnick(nickname)
|
219
|
+
send_command(:getnick, @options[:nickname] = nickname.to_s)
|
220
|
+
read_response # "Nickname set to #{nickname}"
|
221
|
+
self
|
222
|
+
end
|
223
|
+
|
224
|
+
##
|
225
|
+
# Generates a new keypair for the current tunnel.
|
226
|
+
#
|
227
|
+
# @example
|
228
|
+
# bob.newkeys
|
229
|
+
#
|
230
|
+
# @return [Destination]
|
231
|
+
def newkeys
|
232
|
+
send_command(:newkeys)
|
233
|
+
Destination.parse(read_response)
|
234
|
+
end
|
235
|
+
|
236
|
+
##
|
237
|
+
# Returns the destination for the current tunnel.
|
238
|
+
#
|
239
|
+
# @example
|
240
|
+
# bob.getdest
|
241
|
+
#
|
242
|
+
# @return [Destination]
|
243
|
+
# @raise [Error] if no tunnel has been selected
|
244
|
+
def getdest
|
245
|
+
send_command(:getdest)
|
246
|
+
Destination.parse(read_response)
|
247
|
+
end
|
248
|
+
|
249
|
+
##
|
250
|
+
# Returns the key pair for the current tunnel.
|
251
|
+
#
|
252
|
+
# @example
|
253
|
+
# bob.getkeys
|
254
|
+
#
|
255
|
+
# @return [KeyPair]
|
256
|
+
# @raise [Error] if no public key has been set
|
257
|
+
def getkeys
|
258
|
+
send_command(:getkeys)
|
259
|
+
KeyPair.parse(read_response)
|
260
|
+
end
|
261
|
+
|
262
|
+
##
|
263
|
+
# Sets the key pair for the current tunnel.
|
264
|
+
#
|
265
|
+
# @example
|
266
|
+
# bob.setkeys(I2P::KeyPair.parse("..."))
|
267
|
+
#
|
268
|
+
# @param [KeyPair, #to_s] key_pair
|
269
|
+
# @return [void]
|
270
|
+
# @raise [Error] if no tunnel has been selected
|
271
|
+
def setkeys(key_pair)
|
272
|
+
send_command(:setkeys, @options[:keys] = key_pair.respond_to?(:to_base64) ? key_pair.to_base64 : key_pair.to_s)
|
273
|
+
read_response # the Base64-encoded destination
|
274
|
+
self
|
275
|
+
end
|
276
|
+
alias_method :keys=, :setkeys
|
277
|
+
|
278
|
+
##
|
279
|
+
# Sets the inbound host name or IP address that the current tunnel
|
280
|
+
# listens on.
|
281
|
+
#
|
282
|
+
# The default for new tunnels is `inhost("localhost")`.
|
283
|
+
#
|
284
|
+
# @example
|
285
|
+
# bob.inhost('127.0.0.1')
|
286
|
+
#
|
287
|
+
# @param [String, #to_s] host
|
288
|
+
# @return [void]
|
289
|
+
# @raise [Error] if no tunnel has been selected
|
290
|
+
def inhost(host)
|
291
|
+
send_command(:inhost, @options[:inhost] = host.to_s)
|
292
|
+
read_response # "inhost set"
|
293
|
+
self
|
294
|
+
end
|
295
|
+
alias_method :inhost=, :inhost
|
296
|
+
|
297
|
+
##
|
298
|
+
# Sets the inbound port number that the current tunnel listens on.
|
299
|
+
#
|
300
|
+
# @example
|
301
|
+
# bob.inport(37337)
|
302
|
+
#
|
303
|
+
# @param [Integer, #to_i] port
|
304
|
+
# @return [void]
|
305
|
+
# @raise [Error] if no tunnel has been selected
|
306
|
+
def inport(port)
|
307
|
+
send_command(:inport, @options[:inport] = port.to_i)
|
308
|
+
read_response # "inbound port set"
|
309
|
+
self
|
310
|
+
end
|
311
|
+
alias_method :inport=, :inport
|
312
|
+
|
313
|
+
##
|
314
|
+
# Sets the outbound host name or IP address that the current tunnel
|
315
|
+
# connects to.
|
316
|
+
#
|
317
|
+
# The default for new tunnels is `outhost("localhost")`.
|
318
|
+
#
|
319
|
+
# @example
|
320
|
+
# bob.outhost('127.0.0.1')
|
321
|
+
#
|
322
|
+
# @param [String, #to_s] host
|
323
|
+
# @return [void]
|
324
|
+
# @raise [Error] if no tunnel has been selected
|
325
|
+
def outhost(host)
|
326
|
+
send_command(:outhost, @options[:outhost] = host.to_s)
|
327
|
+
read_response # "outhost set"
|
328
|
+
self
|
329
|
+
end
|
330
|
+
alias_method :outhost=, :outhost
|
331
|
+
|
332
|
+
##
|
333
|
+
# Sets the outbound port number that the current tunnel connects to.
|
334
|
+
#
|
335
|
+
# @example
|
336
|
+
# bob.outport(80)
|
337
|
+
#
|
338
|
+
# @param [Integer, #to_i] port
|
339
|
+
# @return [void]
|
340
|
+
# @raise [Error] if no tunnel has been selected
|
341
|
+
def outport(port)
|
342
|
+
send_command(:outport, @options[:output] = port.to_i)
|
343
|
+
read_response # "outbound port set"
|
344
|
+
self
|
345
|
+
end
|
346
|
+
alias_method :outport=, :outport
|
347
|
+
|
348
|
+
##
|
349
|
+
# Toggles whether to send the incoming destination key to listening
|
350
|
+
# sockets.
|
351
|
+
#
|
352
|
+
# This only applies to outbound tunnels and has no effect on inbound
|
353
|
+
# tunnels.
|
354
|
+
#
|
355
|
+
# The default for new tunnels is `quiet(false)`.
|
356
|
+
#
|
357
|
+
# @example Enabling quiet mode
|
358
|
+
# bob.quiet
|
359
|
+
# bob.quiet(true)
|
360
|
+
# bob.quiet = true
|
361
|
+
#
|
362
|
+
# @example Disabling quiet mode
|
363
|
+
# bob.quiet(false)
|
364
|
+
# bob.quiet = false
|
365
|
+
#
|
366
|
+
# @param [Boolean] value
|
367
|
+
# @return [void]
|
368
|
+
# @raise [Error] if no tunnel has been selected
|
369
|
+
def quiet(value = true)
|
370
|
+
send_command(:quiet, @options[:quiet] = value.to_s)
|
371
|
+
read_response # "Quiet set"
|
372
|
+
self
|
373
|
+
end
|
374
|
+
alias_method :quiet=, :quiet
|
375
|
+
|
376
|
+
##
|
377
|
+
# Sets an I2P Control Protocol (I2CP) option for the current tunnel.
|
378
|
+
#
|
379
|
+
# @example
|
380
|
+
# bob.option(key, value)
|
381
|
+
#
|
382
|
+
# @param [String, #to_s] key
|
383
|
+
# @param [String, #to_s] value
|
384
|
+
# @return [void]
|
385
|
+
# @raise [Error] if no tunnel has been selected
|
386
|
+
def option(key, value)
|
387
|
+
send_command(:option, [key, value].join('='))
|
388
|
+
read_response # "#{key} set to #{value}"
|
389
|
+
self
|
390
|
+
end
|
391
|
+
|
392
|
+
##
|
393
|
+
# Starts and activates the current tunnel.
|
394
|
+
#
|
395
|
+
# @example
|
396
|
+
# bob.start
|
397
|
+
#
|
398
|
+
# @return [void]
|
399
|
+
# @raise [Error] if the tunnel settings are incomplete
|
400
|
+
# @raise [Error] if the tunnel is already active
|
401
|
+
def start
|
402
|
+
send_command(:start)
|
403
|
+
read_response # "tunnel starting"
|
404
|
+
self
|
405
|
+
end
|
406
|
+
alias_method :start!, :start
|
407
|
+
|
408
|
+
##
|
409
|
+
# Stops and inactivates the current tunnel.
|
410
|
+
#
|
411
|
+
# @example
|
412
|
+
# bob.stop
|
413
|
+
#
|
414
|
+
# @return [void]
|
415
|
+
# @raise [Error] if no tunnel has been selected
|
416
|
+
# @raise [Error] if the tunnel is already inactive
|
417
|
+
def stop
|
418
|
+
send_command(:stop)
|
419
|
+
read_response # "tunnel stopping"
|
420
|
+
self
|
421
|
+
end
|
422
|
+
alias_method :stop!, :stop
|
423
|
+
|
424
|
+
##
|
425
|
+
# Removes the current tunnel. The tunnel must be inactive.
|
426
|
+
#
|
427
|
+
# @example
|
428
|
+
# bob.clear
|
429
|
+
#
|
430
|
+
# @return [void]
|
431
|
+
# @raise [Error] if no tunnel has been selected
|
432
|
+
# @raise [Error] if the tunnel is still active
|
433
|
+
def clear
|
434
|
+
send_command(:clear)
|
435
|
+
read_response # "cleared"
|
436
|
+
self
|
437
|
+
end
|
438
|
+
alias_method :clear!, :clear
|
439
|
+
|
440
|
+
protected
|
441
|
+
|
442
|
+
##
|
443
|
+
# Sends a command over the BOB bridge socket.
|
444
|
+
#
|
445
|
+
# @param [String, #to_s] command
|
446
|
+
# @param [Array<#to_s>] args
|
447
|
+
# @return [void]
|
448
|
+
def send_command(command, *args)
|
449
|
+
send_line([command.to_s, *args].join(' '))
|
450
|
+
end
|
451
|
+
|
452
|
+
##
|
453
|
+
# Sends a text line over the BOB bridge socket.
|
454
|
+
#
|
455
|
+
# @param [String, #to_s] line
|
456
|
+
# @return [void]
|
457
|
+
def send_line(line)
|
458
|
+
connect unless connected?
|
459
|
+
warn "-> #{line}" if @options[:debug]
|
460
|
+
@socket.write(line.to_s + "\n")
|
461
|
+
@socket.flush
|
462
|
+
self
|
463
|
+
end
|
464
|
+
|
465
|
+
##
|
466
|
+
# Reads a response from the BOB bridge socket.
|
467
|
+
#
|
468
|
+
# @return [Object]
|
469
|
+
# @raise [Error] on an ERROR response
|
470
|
+
def read_response
|
471
|
+
case line = read_line
|
472
|
+
when 'OK' then true
|
473
|
+
when /^OK\s*(.*)$/ then $1.strip
|
474
|
+
when /^ERROR\s+(.*)$/ then raise Error.new($1.strip)
|
475
|
+
else line
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
##
|
480
|
+
# Reads a text line from the BOB bridge socket.
|
481
|
+
#
|
482
|
+
# @return [String]
|
483
|
+
def read_line
|
484
|
+
line = @socket.readline.chomp
|
485
|
+
warn "<- #{line}" if @options[:debug]
|
486
|
+
line
|
487
|
+
end
|
488
|
+
end
|
489
|
+
end; end
|