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 ADDED
@@ -0,0 +1 @@
1
+ * Arto Bendiken <arto.bendiken@gmail.com>
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
@@ -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
@@ -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
@@ -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