i2p 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|