mtik 3.1.2 → 4.0.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.
- checksums.yaml +7 -0
- data/CHANGELOG.txt +32 -1
- data/README.txt +1 -1
- data/Rakefile +5 -36
- data/VERSION.txt +1 -1
- data/examples/tikjson.rb +4 -2
- data/lib/mtik.rb +70 -23
- data/lib/mtik/connection.rb +171 -46
- data/lib/mtik/request.rb +2 -2
- metadata +22 -42
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6b5f8b42fee32a8aa6397a13fab80370d50af593ec95862dd6d7f2f483a41000
|
4
|
+
data.tar.gz: 67856a1a7ffc030967c3ec9ab265c1e1d8189813af6d84b6522fbcc2a397fbdb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 81be2978391702f448cc17d10dac4ed7facc7684a4a4270395e7524bc70a41bdfe21d6062a7ec21ca02d1b280352166162c59d099a0e4d1c944e10c68dceafe8
|
7
|
+
data.tar.gz: f6e52a8ba3d6abe04553b4631d7bbef9aa43c25a55b2fbfdf961041ffb6dce488af750ecc7320ecef31322e61a5618785b216921b1a5c868354c5fa9735b1c64
|
data/CHANGELOG.txt
CHANGED
@@ -1,3 +1,33 @@
|
|
1
|
+
2014-02-14 (14 FEB 2014) VERSION 4.2.3 Aaron D. Gifford (http://www.aarongifford.com)
|
2
|
+
* Update to fetch() utility, along with some very minor some cosmetic changes
|
3
|
+
|
4
|
+
2013-06-06 (06 JUN 2013) VERSION 4.0.2 Aaron D. Gifford (http://www.aarongifford.com)
|
5
|
+
Bart Braem (http://www.lalunerouge.net/)
|
6
|
+
* Merged Bart Braem's implementation of timeouts and bumped up the version. Thanks, Bart!
|
7
|
+
* Updated Rakefile to remove a bit of obsolescence
|
8
|
+
|
9
|
+
2012-02-09 (09 FEB 2012) VERSION 4.0.1 Aaron D. Gifford (http://www.aarongifford.com)
|
10
|
+
* Added os_version to connections. Upon successful connect and login, the RouterOS
|
11
|
+
version is fetched and stored. This will allow future updates to better support
|
12
|
+
some commands that differ (like fetch) depending on which RouterOS version is
|
13
|
+
installed on the device.
|
14
|
+
|
15
|
+
2011-03-25 (25 MAR 2011) VERSION 4.0.0 Aaron D. Gifford (http://www.aarongifford.com)
|
16
|
+
* Per user suggestion, added a new optional cancel parameter to the MTik#command()
|
17
|
+
method that will auto-cancel the supplied command after receiving the specified
|
18
|
+
number of '!re' reply sentences. This is usful for executing a command that otherwise
|
19
|
+
will not terminate, but will keep sending output perpetually if not canceled.
|
20
|
+
* Spelling changes: :cancelled updated to :canceled This means anyone who checked the
|
21
|
+
state of a request using :cancelled or 'cancelled' will need to update their code to
|
22
|
+
check for :canceled instead.
|
23
|
+
* Due to changing of spelling and adding a new parameter, I've bumped the major version
|
24
|
+
number to 4.x in case any users code might break. This in spite of the fact that
|
25
|
+
there are no major new features added.
|
26
|
+
* I found 2-3 tiny bugs left over from the past change of request state from string
|
27
|
+
to symbol and fixed those, updated error messages to reflect state as a symbol,
|
28
|
+
eliminated a few redundant key?() calls, and fixed a replycounter initialization
|
29
|
+
typo (had set it to 1 instead of 0).
|
30
|
+
|
1
31
|
2011-01-11 (11 JAN 2011) VERSION 3.1.2 Aaron D. Gifford (http://www.aarongifford.com)
|
2
32
|
* Added source file encoding comments and updated the copyright notices
|
3
33
|
* Fixed a tiny bug in lib/mtik/connection.rb
|
@@ -18,7 +48,7 @@
|
|
18
48
|
which should not affect the API and should be backward compatible. By default,
|
19
49
|
there is no inactivity timeout for downloads. But if you set the timeout parameter
|
20
50
|
to a positive number, when a reply arrives and no progress/activity has been
|
21
|
-
made for timeout seconds, the command will be
|
51
|
+
made for timeout seconds, the command will be canceled. This should help with
|
22
52
|
stalled downloads (i.e. the remote side has stopped sending but the TCP connection
|
23
53
|
remains open/active).
|
24
54
|
* Also add the MTik::Request object as a parameter to the MTik::Connection.fetch()
|
@@ -70,3 +100,4 @@
|
|
70
100
|
fetch.rb
|
71
101
|
* Added VERSION.txt, CHANGELOG.txt, README.txt, LICENSE.txt, and *.gemspec files, moved
|
72
102
|
the example files into the bin subdirectory
|
103
|
+
|
data/README.txt
CHANGED
data/Rakefile
CHANGED
@@ -1,46 +1,15 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require '
|
3
|
-
require '
|
2
|
+
require 'rubygems/package_task'
|
3
|
+
require 'rdoc/task'
|
4
4
|
|
5
|
-
gemspec = Gem::Specification.
|
6
|
-
spec.name = 'mtik'
|
7
|
-
spec.version = File.open('VERSION.txt','r').to_a.join.strip
|
8
|
-
spec.date = File.mtime('VERSION.txt')
|
9
|
-
spec.author = 'Aaron D. Gifford'
|
10
|
-
spec.email = 'email_not_accepted@aarongifford.com'
|
11
|
-
spec.homepage = 'http://www.aarongifford.com/computers/mtik/'
|
12
|
-
spec.summary = 'MTik implements the MikroTik RouterOS API for use in Ruby.'
|
13
|
-
spec.description = 'MTik implements the MikroTik RouterOS API for use in Ruby.'
|
14
|
-
spec.rubyforge_project = 'mtik'
|
15
|
-
spec.extra_rdoc_files = [ 'README.txt' ]
|
16
|
-
spec.require_paths = [ 'lib' ]
|
17
|
-
spec.files = [
|
18
|
-
'CHANGELOG.txt',
|
19
|
-
'LICENSE.txt',
|
20
|
-
'README.txt',
|
21
|
-
'VERSION.txt',
|
22
|
-
'Rakefile',
|
23
|
-
'examples/tikjson.rb',
|
24
|
-
'bin/tikcli',
|
25
|
-
'bin/tikcommand',
|
26
|
-
'bin/tikfetch',
|
27
|
-
'lib/mtik.rb',
|
28
|
-
'lib/mtik/connection.rb',
|
29
|
-
'lib/mtik/error.rb',
|
30
|
-
'lib/mtik/fatalerror.rb',
|
31
|
-
'lib/mtik/reply.rb',
|
32
|
-
'lib/mtik/request.rb',
|
33
|
-
'lib/mtik/timeouterror.rb'
|
34
|
-
]
|
35
|
-
spec.executables = [ 'tikcli', 'tikcommand', 'tikfetch' ]
|
36
|
-
end
|
5
|
+
gemspec = Gem::Specification.load('mtik.gemspec')
|
37
6
|
|
38
|
-
|
7
|
+
Gem::PackageTask.new(gemspec) do |pkg|
|
39
8
|
pkg.need_zip = true
|
40
9
|
pkg.need_tar = true
|
41
10
|
end
|
42
11
|
|
43
|
-
|
12
|
+
RDoc::Task.new do |rdoc|
|
44
13
|
rdoc.name = 'rdoc'
|
45
14
|
rdoc.main = 'README.txt'
|
46
15
|
rdoc.rdoc_dir = 'doc'
|
data/VERSION.txt
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
4.0.4
|
data/examples/tikjson.rb
CHANGED
@@ -2,11 +2,13 @@
|
|
2
2
|
########################################################################
|
3
3
|
#--
|
4
4
|
#
|
5
|
-
# FILE:
|
5
|
+
# FILE: tikjson.rb -- Example of using the Ruby MikroTik API in Ruby
|
6
|
+
# to execute an API command and retrieve results
|
7
|
+
# in JSON format
|
6
8
|
#
|
7
9
|
#++
|
8
10
|
# Author:: Aaron D. Gifford - http://www.aarongifford.com/
|
9
|
-
# Copyright:: Copyright (c) 2009-
|
11
|
+
# Copyright:: Copyright (c) 2009-2014, InfoWest, Inc.
|
10
12
|
# License:: BSD license
|
11
13
|
#--
|
12
14
|
# Redistribution and use in source and binary forms, with or without
|
data/lib/mtik.rb
CHANGED
@@ -34,15 +34,17 @@
|
|
34
34
|
# encoding: ASCII-8BIT
|
35
35
|
|
36
36
|
module MTik
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
37
|
+
require_relative 'mtik/error.rb'
|
38
|
+
require_relative 'mtik/fatalerror.rb'
|
39
|
+
require_relative 'mtik/timeouterror.rb'
|
40
|
+
require_relative 'mtik/request.rb'
|
41
|
+
require_relative 'mtik/reply.rb'
|
42
|
+
require_relative 'mtik/connection.rb'
|
43
43
|
|
44
44
|
## Default MikroTik RouterOS API TCP port:
|
45
45
|
PORT = 8728
|
46
|
+
## Default MikroTik RouterOS API-SSL TCP port:
|
47
|
+
PORT_SSL = 8729
|
46
48
|
## Default username to use if none is specified:
|
47
49
|
USER = 'admin'
|
48
50
|
## Default password to use if none is specified:
|
@@ -53,6 +55,12 @@ module MTik
|
|
53
55
|
## API data when expecting one or more command responses.
|
54
56
|
CMD_TIMEOUT = 60
|
55
57
|
|
58
|
+
## Maximum number of replies before a command is auto-canceled:
|
59
|
+
MAXREPLIES = 1000
|
60
|
+
|
61
|
+
## SSL is set to false by default
|
62
|
+
USE_SSL = false
|
63
|
+
|
56
64
|
@verbose = false
|
57
65
|
@debug = false
|
58
66
|
|
@@ -120,8 +128,8 @@ module MTik
|
|
120
128
|
## Auto-cancel any '/tool/fetch' commands that have finished,
|
121
129
|
## or commands that have received the specified number of
|
122
130
|
## replies:
|
123
|
-
if req.state
|
124
|
-
cmd == '/tool/fetch' && sentence
|
131
|
+
if req.state == :sent && (
|
132
|
+
cmd == '/tool/fetch' && sentence['status'] == 'finished'
|
125
133
|
) || (maxreply > 0 && count == maxreply)
|
126
134
|
state = 2
|
127
135
|
req.cancel do |r, s|
|
@@ -190,12 +198,52 @@ module MTik
|
|
190
198
|
## more commands, retrieve the response(s), close the connection,
|
191
199
|
## and return the response(s).
|
192
200
|
##
|
193
|
-
##
|
194
|
-
##
|
195
|
-
##
|
196
|
-
##
|
197
|
-
##
|
198
|
-
##
|
201
|
+
## PARAMETERS:
|
202
|
+
## All parameters supplied to this method are contained in a
|
203
|
+
## single hash. Here are available hash keys:
|
204
|
+
##
|
205
|
+
## :host => the host name or IP to connect to
|
206
|
+
## :user => the API user ID to authenticate with
|
207
|
+
## :pass => the API password to authenticate with
|
208
|
+
## :command => one or more API commands to execute
|
209
|
+
## :limit => an OPTIONAL integer reply limit
|
210
|
+
##
|
211
|
+
## The :command parameter may be:
|
212
|
+
## * A single string representing a single API command to execute
|
213
|
+
## * An array of strings in which case the first string is the API
|
214
|
+
## command to execute and each subsequent array item is an API
|
215
|
+
## parameter or argument.
|
216
|
+
## * An array of arrays -- Multiple API command may be executed
|
217
|
+
## in sequence. Each subarray is an array of strings containing
|
218
|
+
## an API command and zero or more parameters.
|
219
|
+
##
|
220
|
+
## The :limit parameter if present specifies an integer. This parameter
|
221
|
+
## is to be used whenever executing one or more API commands that do
|
222
|
+
## not terminate with a '!done' response sentence, but instead keep
|
223
|
+
## sending '!re' reply sentences.
|
224
|
+
##
|
225
|
+
## An exception is the '/tools/fetch' API command, which this method
|
226
|
+
## will auto-cancel when it finishes.
|
227
|
+
##
|
228
|
+
## Regarding the :limit parameter:
|
229
|
+
## * If present and the integer is zero or negative, THERE WILL BE
|
230
|
+
## NO LIMIT ENFORCED on the number of replies from each API command.
|
231
|
+
## *WARNING:* If you do NOT limit the number of replies when
|
232
|
+
## executing an API command like <i>"/interface/montitor-traffic"</i>
|
233
|
+
## this method may not ever terminate and may consume memory
|
234
|
+
## buffering replies until resources are exhausted.
|
235
|
+
## * If present and a positive integer, each API command may at
|
236
|
+
## most receive the specified number of reply sentences, after which
|
237
|
+
## the command will automatically be canceled. This is useful
|
238
|
+
## to terminate commands that would otherwise keep sending output
|
239
|
+
## forever.
|
240
|
+
## * If NOT present, or if nil, the default reply limit as contained
|
241
|
+
## in the MAXREPLIES constant will be enforced. *WARNING:* This
|
242
|
+
## default limit could be so large that this method would not
|
243
|
+
## return for a long time, waiting for the number of replies.
|
244
|
+
##
|
245
|
+
## Remember that the limit applies separately to each API command
|
246
|
+
## executed.
|
199
247
|
def self.command(args)
|
200
248
|
tk = MTik::Connection.new(
|
201
249
|
:host => args[:host],
|
@@ -205,6 +253,7 @@ module MTik
|
|
205
253
|
:conn_timeout => args[:conn_timeout],
|
206
254
|
:cmd_timeout => args[:cmd_timeout]
|
207
255
|
)
|
256
|
+
limit = args[:limit] ## Optional reply limit
|
208
257
|
cmd = args[:command]
|
209
258
|
replies = Array.new
|
210
259
|
if cmd.is_a?(String)
|
@@ -217,20 +266,18 @@ module MTik
|
|
217
266
|
|
218
267
|
cmd.each_index do |i|
|
219
268
|
c = cmd[i]
|
220
|
-
replycount =
|
269
|
+
replycount = 0
|
221
270
|
tk.send_request(false, c[0], c[1,c.length-1]) do |req, sentence|
|
222
271
|
replycount += 1
|
223
272
|
if c[0] == '/tool/fetch'
|
224
|
-
if
|
225
|
-
sentence.key?('status') &&
|
226
|
-
sentence['status'] == 'finished' &&
|
227
|
-
req.state != 'cancelled'
|
228
|
-
)
|
273
|
+
if sentence['status'] == 'finished' && req.state == :sent
|
229
274
|
## Cancel 'finished' fetch commands
|
230
275
|
req.cancel
|
231
276
|
end
|
232
|
-
elsif
|
233
|
-
|
277
|
+
elsif req.state == :sent && (
|
278
|
+
limit.nil? ? (replycount >= MAXREPLIES) : (limit > 0 && replycount >= limit)
|
279
|
+
)
|
280
|
+
## Auto-cancel any command after the maximum number of replies:
|
234
281
|
req.cancel
|
235
282
|
end
|
236
283
|
if sentence.key?('!done')
|
@@ -239,7 +286,7 @@ module MTik
|
|
239
286
|
end
|
240
287
|
end
|
241
288
|
|
242
|
-
tk.wait_all ## Event loop -- wait for all commands
|
289
|
+
tk.wait_all ## Event loop -- wait for all commands to finish
|
243
290
|
tk.get_reply('/quit')
|
244
291
|
tk.close
|
245
292
|
return replies
|
data/lib/mtik/connection.rb
CHANGED
@@ -39,6 +39,7 @@
|
|
39
39
|
class MTik::Connection
|
40
40
|
require 'socket'
|
41
41
|
require 'digest/md5'
|
42
|
+
require 'openssl'
|
42
43
|
|
43
44
|
## Initialize/construct the new _MTik_ object. One or more
|
44
45
|
## key/value pair style arguments must be specified. The one
|
@@ -46,25 +47,31 @@ class MTik::Connection
|
|
46
47
|
## to.
|
47
48
|
## +host+:: This is the only _required_ argument. Example:
|
48
49
|
## <i> :host => "rb411.example.org" </i>
|
49
|
-
## +
|
50
|
+
## +ssl+:: Use SSL to encrypt communications
|
51
|
+
## +port+:: Override the default API port (8728/8729)
|
50
52
|
## +user+:: Override the default API username ('admin')
|
51
53
|
## +pass+:: Override the default API password (blank)
|
52
54
|
## +conn_timeout+:: Override the default connection
|
53
|
-
## timeout (60 seconds)
|
55
|
+
## timeout (60 seconds)
|
54
56
|
## +cmd_timeout+:: Override the default command timeout
|
55
57
|
## (60 seconds) -- the number of seconds
|
56
58
|
## to wait for additional API input.
|
59
|
+
## +unencrypted_plaintext+:: Attempt to use the 6.43+ login API even without SSL
|
57
60
|
def initialize(args)
|
58
|
-
@sock
|
59
|
-
@
|
60
|
-
@
|
61
|
-
@
|
62
|
-
@
|
63
|
-
@
|
64
|
-
@
|
65
|
-
@
|
66
|
-
@
|
67
|
-
@
|
61
|
+
@sock = nil
|
62
|
+
@ssl_sock = nil
|
63
|
+
@requests = Hash.new
|
64
|
+
@use_ssl = args[:ssl] || MTik::USE_SSL
|
65
|
+
@unencrypted_plaintext = args[:unecrypted_plaintext]
|
66
|
+
@host = args[:host]
|
67
|
+
@port = args[:port] || (@use_ssl ? MTik::PORT_SSL : MTik::PORT)
|
68
|
+
@user = args[:user] || MTik::USER
|
69
|
+
@pass = args[:pass] || MTik::PASS
|
70
|
+
@conn_timeout = args[:conn_timeout] || MTik::CONN_TIMEOUT
|
71
|
+
@cmd_timeout = args[:cmd_timeout] || MTik::CMD_TIMEOUT
|
72
|
+
@data = ''
|
73
|
+
@parsing = false ## Recursion flag
|
74
|
+
@os_version = nil
|
68
75
|
|
69
76
|
## Initiate connection and immediately login to device:
|
70
77
|
login
|
@@ -74,7 +81,8 @@ class MTik::Connection
|
|
74
81
|
def outstanding
|
75
82
|
return @requests.length
|
76
83
|
end
|
77
|
-
attr_reader :requests, :host, :port, :user, :pass, :conn_timeout, :cmd_timeout
|
84
|
+
attr_reader :requests, :host, :port, :user, :pass, :conn_timeout, :cmd_timeout,
|
85
|
+
:os_version
|
78
86
|
|
79
87
|
## Internal utility function:
|
80
88
|
## Sugar-coat ["0deadf0015"].pack('H*') so one can just do
|
@@ -93,37 +101,72 @@ class MTik::Connection
|
|
93
101
|
raise MTik::Error.new("Login failed: Unable to connect to device.")
|
94
102
|
end
|
95
103
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
104
|
+
# Try using the the post-6.43 login API; on older routers this still initiates
|
105
|
+
# a regular challenge-response cycle.
|
106
|
+
if @use_ssl || @unencrypted_plaintext
|
107
|
+
warn("SENDING PLAINTEXT PASSWORD OVER UNENCRYPTED CONNECTION") unless @use_ssl
|
108
|
+
reply = get_reply('/login',["=name=#{@user}","=password=#{@pass}"])
|
109
|
+
if reply.length == 1 && reply[0].length == 2 && reply[0].key?('!done')
|
110
|
+
v_6_43_login_successful = true
|
111
|
+
end
|
112
|
+
else
|
113
|
+
## Just send first /login command to obtain the challenge, if not using SSL
|
114
|
+
reply = get_reply('/login')
|
101
115
|
end
|
102
116
|
|
103
|
-
|
104
|
-
|
117
|
+
unless v_6_43_login_successful
|
118
|
+
## Make sure the reply has the info we expect for challenge-response authentication:
|
119
|
+
if reply.length != 1 || reply[0].length != 3 || !reply[0].key?('ret')
|
120
|
+
raise MTik::Error.new("Login failed: unexpected reply to login attempt.")
|
121
|
+
end
|
105
122
|
|
106
|
-
|
107
|
-
|
123
|
+
## Grab the challenge from first (only) sentence in the reply:
|
124
|
+
challenge = hex2bin(reply[0]['ret'])
|
108
125
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
126
|
+
## Generate reply MD5 hash and convert binary hash to hex string:
|
127
|
+
response = Digest::MD5.hexdigest(0.chr + @pass + challenge)
|
128
|
+
|
129
|
+
## Send second /login command with our response:
|
130
|
+
reply = get_reply('/login', '=name=' + @user, '=response=00' + response)
|
131
|
+
if reply[0].key?('!trap')
|
132
|
+
raise MTik::Error.new("Login failed: " + (reply[0].key?('message') ? reply[0]['message'] : 'Unknown error.'))
|
133
|
+
end
|
134
|
+
unless reply.length == 1 && reply[0].length == 2 && reply[0].key?('!done')
|
135
|
+
@sock.close
|
136
|
+
@sock = nil
|
137
|
+
raise MTik::Error.new('Login failed: Unknown response to login.')
|
138
|
+
end
|
113
139
|
end
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
140
|
+
|
141
|
+
## Request the RouterOS version of the device as different versions
|
142
|
+
## sometimes use slightly different command parameters:
|
143
|
+
reply = get_reply('/system/resource/getall')
|
144
|
+
if reply.first.key?('!re') && reply.first['version']
|
145
|
+
@os_version = reply.first['version']
|
118
146
|
end
|
119
147
|
end
|
120
148
|
|
121
149
|
## Connect to the device
|
122
150
|
def connect
|
123
151
|
return unless @sock.nil?
|
124
|
-
## TODO: Perhaps catch more errors
|
152
|
+
## TODO: Perhaps catch more errors
|
125
153
|
begin
|
126
|
-
|
154
|
+
addr = Socket.getaddrinfo(@host, nil)
|
155
|
+
@sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
|
156
|
+
|
157
|
+
begin
|
158
|
+
@sock.connect_nonblock(Socket.pack_sockaddr_in(@port, addr[0][3]))
|
159
|
+
rescue Errno::EINPROGRESS
|
160
|
+
ready = IO.select([@sock], [@sock], [], @conn_timeout)
|
161
|
+
if ready
|
162
|
+
@sock
|
163
|
+
else
|
164
|
+
raise Errno::ETIMEDOUT
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
connect_ssl(@sock) if @use_ssl
|
169
|
+
|
127
170
|
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, Errno::ENETUNREACH,
|
128
171
|
Errno::EHOSTUNREACH => e
|
129
172
|
@sock = nil
|
@@ -131,6 +174,17 @@ class MTik::Connection
|
|
131
174
|
end
|
132
175
|
end
|
133
176
|
|
177
|
+
def connect_ssl(sock)
|
178
|
+
ssl_context = OpenSSL::SSL::SSLContext.new()
|
179
|
+
ssl_context.ciphers = ['HIGH']
|
180
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(sock, ssl_context)
|
181
|
+
ssl_socket.sync_close = true
|
182
|
+
unless ssl_socket.connect
|
183
|
+
raise MTik::Error.new("Cannot establish SSL connection.")
|
184
|
+
end
|
185
|
+
@ssl_sock = ssl_socket
|
186
|
+
end
|
187
|
+
|
134
188
|
## Wait for and read exactly one sentence, regardless of content:
|
135
189
|
def get_sentence
|
136
190
|
## TODO: Implement timeouts, detect disconnection, maybe do auto-reconnect
|
@@ -184,7 +238,8 @@ class MTik::Connection
|
|
184
238
|
end
|
185
239
|
oldlen = @data.length
|
186
240
|
## Read some more data IF any is available:
|
187
|
-
|
241
|
+
sock = @ssl_sock || @sock
|
242
|
+
sel = IO.select([sock],nil,[sock], @cmd_timeout)
|
188
243
|
if sel.nil?
|
189
244
|
raise MTik::TimeoutError.new(
|
190
245
|
"Time-out while awaiting data with #{outstanding} pending " +
|
@@ -192,7 +247,7 @@ class MTik::Connection
|
|
192
247
|
)
|
193
248
|
end
|
194
249
|
if sel[0].length == 1
|
195
|
-
@data +=
|
250
|
+
@data += recv(8192)
|
196
251
|
elsif sel[2].length == 1
|
197
252
|
raise MTik::Error.new(
|
198
253
|
"I/O (select) error while awaiting data with #{outstanding} pending " +
|
@@ -235,7 +290,7 @@ class MTik::Connection
|
|
235
290
|
sentence = get_sentence ## This call must be ATOMIC or re-entrant safety fails
|
236
291
|
|
237
292
|
## Check for '!fatal' before checking for a tag--'!fatal'
|
238
|
-
## is never(???) tagged:
|
293
|
+
## is never(???) tagged:
|
239
294
|
if sentence.key?('!fatal')
|
240
295
|
## FATAL ERROR has occured! (Or a '/quit' command was issued...)
|
241
296
|
if @data.length > 0
|
@@ -334,7 +389,7 @@ class MTik::Connection
|
|
334
389
|
## +args+:: Zero or more arguments to the command
|
335
390
|
## +callback+:: Proc/lambda code (or code block if not provided as
|
336
391
|
## an argument) to be called. (See the +await_completion+
|
337
|
-
##
|
392
|
+
##
|
338
393
|
def send_request(await_completion, command, *args, &callback)
|
339
394
|
if await_completion.is_a?(MTik::Request)
|
340
395
|
req = await_completion
|
@@ -362,10 +417,41 @@ class MTik::Connection
|
|
362
417
|
|
363
418
|
## Send the request object over the socket
|
364
419
|
def xmit(req)
|
365
|
-
@
|
420
|
+
if @ssl_sock
|
421
|
+
@ssl_sock.write(req.request)
|
422
|
+
else
|
423
|
+
@sock.send(req.request, 0)
|
424
|
+
end
|
425
|
+
|
366
426
|
return req
|
367
427
|
end
|
368
428
|
|
429
|
+
def recv(buffer_size)
|
430
|
+
if @ssl_sock
|
431
|
+
recv_openssl(buffer_size)
|
432
|
+
else
|
433
|
+
@sock.recv(buffer_size)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
# 2 cases for backwards compatibility
|
438
|
+
def recv_openssl(buffer_size)
|
439
|
+
if OpenSSL::SSL.const_defined? 'SSLErrorWaitReadable'.freeze
|
440
|
+
begin
|
441
|
+
@ssl_sock.read_nonblock(buffer_size)
|
442
|
+
rescue OpenSSL::SSL::SSLErrorWaitReadable
|
443
|
+
''
|
444
|
+
end
|
445
|
+
else
|
446
|
+
begin
|
447
|
+
@ssl_sock.read_nonblock(buffer_size)
|
448
|
+
rescue OpenSSL::SSL::SSLError => e
|
449
|
+
return '' if e.message == 'read would block'.freeze
|
450
|
+
raise e
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
369
455
|
## Send a command, then wait for the command to complete, then return
|
370
456
|
## the completed reply.
|
371
457
|
##
|
@@ -398,8 +484,10 @@ class MTik::Connection
|
|
398
484
|
|
399
485
|
## Close the connection.
|
400
486
|
def close
|
401
|
-
return if @sock.nil?
|
402
|
-
@
|
487
|
+
return if @sock.nil? and @ssl_sock.nil?
|
488
|
+
@ssl_sock.close if @ssl_sock and !@ssl_sock.closed?
|
489
|
+
@sock.close if @sock and !@sock.closed?
|
490
|
+
@ssl_sock = nil
|
403
491
|
@sock = nil
|
404
492
|
end
|
405
493
|
|
@@ -503,16 +591,53 @@ class MTik::Connection
|
|
503
591
|
## +total+:: Final expected file size in bytes
|
504
592
|
## +bytes+:: Number of bytes transferred so far
|
505
593
|
## +request+:: The MTik::Request object
|
506
|
-
def fetch(url, filename, timeout=nil, &callback)
|
594
|
+
def fetch(url, filename=nil, timeout=nil, &callback)
|
595
|
+
require 'uri'
|
596
|
+
|
597
|
+
uri = URI(url)
|
598
|
+
filename = File.basename(uri.path) if filename.nil?
|
599
|
+
|
507
600
|
total = bytes = oldbytes = 0
|
508
601
|
status = ''
|
509
602
|
done = false
|
510
603
|
lastactivity = Time.now
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
604
|
+
|
605
|
+
## RouterOS versions 4.9 and prior (not sure if this version cut-off
|
606
|
+
## is exactly right) would accept the url parameter, but failed to
|
607
|
+
## download the files. So for versions older than this, we'll use
|
608
|
+
## the mode/src-path/port parameters instead if possible.
|
609
|
+
if !@os_version.nil? && lambda {|a,b|
|
610
|
+
sr = %r{(?:\.|rc|beta|alpha)}
|
611
|
+
a = a.split(sr).map{|i| i.to_i}
|
612
|
+
b = b.split(sr).map{|i| i.to_i}
|
613
|
+
i = 0
|
614
|
+
while i < a.size && i < b.size
|
615
|
+
return -1 if a[i] < b[i]
|
616
|
+
return 1 if a[i] > b[i]
|
617
|
+
i += 1
|
618
|
+
end
|
619
|
+
return a.size <=> b.size
|
620
|
+
}.call(@os_version, '4.9') < 1
|
621
|
+
command = [
|
622
|
+
'/tool/fetch', '=mode=' + uri.scheme,
|
623
|
+
'=src-path=' + uri.path + (uri.query.size > 0 ? '?' + uri.query : ''),
|
624
|
+
'=dst-path=' + filename
|
625
|
+
]
|
626
|
+
case uri.scheme
|
627
|
+
when 'http'
|
628
|
+
command << '=port=80'
|
629
|
+
when 'https'
|
630
|
+
command << '=port=443'
|
631
|
+
end
|
632
|
+
else
|
633
|
+
command = [
|
634
|
+
'/tool/fetch',
|
635
|
+
'=url=' + url,
|
636
|
+
'=dst-path=' + filename
|
637
|
+
]
|
638
|
+
end
|
639
|
+
|
640
|
+
req = get_reply_each(command[0], *command[1..-1]) do |r, s|
|
516
641
|
if s.key?('!re') && !done
|
517
642
|
unless s.key?('status')
|
518
643
|
raise MTik::Error.new("Unknown response to '/tool/fetch': missing 'status' in response.")
|
@@ -602,7 +727,7 @@ class MTik::Connection
|
|
602
727
|
else
|
603
728
|
raise MTik::Error.new("Invalid settings match class '#{keyitem}' (expected Array, Regexp, or String)")
|
604
729
|
end
|
605
|
-
|
730
|
+
|
606
731
|
if s.key?(key)
|
607
732
|
## A key matches! && s[k] != v
|
608
733
|
oldv = s[k]
|
data/lib/mtik/request.rb
CHANGED
@@ -242,7 +242,7 @@ class MTik::Request < Array
|
|
242
242
|
if @state != :sent
|
243
243
|
raise MTik::Error.new(
|
244
244
|
"Method MTik::Request#cancel() called with state '#{@state}' " +
|
245
|
-
"(should only call when state is
|
245
|
+
"(should only call when state is :sent)"
|
246
246
|
)
|
247
247
|
end
|
248
248
|
@conn.send_request(true, '/cancel', '=tag=' + @tag, &callback)
|
@@ -254,7 +254,7 @@ class MTik::Request < Array
|
|
254
254
|
if @state != :sent
|
255
255
|
raise MTik::Error.new(
|
256
256
|
"Method MTik::Request#cancel() called with state '#{@state}' " +
|
257
|
-
"(should only call when state is
|
257
|
+
"(should only call when state is :sent)"
|
258
258
|
)
|
259
259
|
end
|
260
260
|
@conn.send_request(false, '/cancel', '=tag=' + @tag, &callback)
|
metadata
CHANGED
@@ -1,43 +1,34 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: mtik
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
segments:
|
6
|
-
- 3
|
7
|
-
- 1
|
8
|
-
- 2
|
9
|
-
version: 3.1.2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 4.0.4
|
10
5
|
platform: ruby
|
11
|
-
authors:
|
6
|
+
authors:
|
12
7
|
- Aaron D. Gifford
|
13
8
|
autorequire:
|
14
9
|
bindir: bin
|
15
10
|
cert_chain: []
|
16
|
-
|
17
|
-
date: 2011-01-11 00:00:00 -07:00
|
18
|
-
default_executable:
|
11
|
+
date: 2019-07-26 00:00:00.000000000 Z
|
19
12
|
dependencies: []
|
20
|
-
|
21
13
|
description: MTik implements the MikroTik RouterOS API for use in Ruby.
|
22
14
|
email: email_not_accepted@aarongifford.com
|
23
|
-
executables:
|
15
|
+
executables:
|
24
16
|
- tikcli
|
25
17
|
- tikcommand
|
26
18
|
- tikfetch
|
27
19
|
extensions: []
|
28
|
-
|
29
|
-
extra_rdoc_files:
|
20
|
+
extra_rdoc_files:
|
30
21
|
- README.txt
|
31
|
-
files:
|
22
|
+
files:
|
32
23
|
- CHANGELOG.txt
|
33
24
|
- LICENSE.txt
|
34
25
|
- README.txt
|
35
|
-
- VERSION.txt
|
36
26
|
- Rakefile
|
37
|
-
-
|
27
|
+
- VERSION.txt
|
38
28
|
- bin/tikcli
|
39
29
|
- bin/tikcommand
|
40
30
|
- bin/tikfetch
|
31
|
+
- examples/tikjson.rb
|
41
32
|
- lib/mtik.rb
|
42
33
|
- lib/mtik/connection.rb
|
43
34
|
- lib/mtik/error.rb
|
@@ -45,37 +36,26 @@ files:
|
|
45
36
|
- lib/mtik/reply.rb
|
46
37
|
- lib/mtik/request.rb
|
47
38
|
- lib/mtik/timeouterror.rb
|
48
|
-
has_rdoc: true
|
49
39
|
homepage: http://www.aarongifford.com/computers/mtik/
|
50
40
|
licenses: []
|
51
|
-
|
41
|
+
metadata: {}
|
52
42
|
post_install_message:
|
53
43
|
rdoc_options: []
|
54
|
-
|
55
|
-
require_paths:
|
44
|
+
require_paths:
|
56
45
|
- lib
|
57
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
-
|
59
|
-
requirements:
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
60
48
|
- - ">="
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
-
none: false
|
67
|
-
requirements:
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
68
53
|
- - ">="
|
69
|
-
- !ruby/object:Gem::Version
|
70
|
-
|
71
|
-
- 0
|
72
|
-
version: "0"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
73
56
|
requirements: []
|
74
|
-
|
75
|
-
rubyforge_project: mtik
|
76
|
-
rubygems_version: 1.3.7
|
57
|
+
rubygems_version: 3.0.4
|
77
58
|
signing_key:
|
78
|
-
specification_version:
|
59
|
+
specification_version: 4
|
79
60
|
summary: MTik implements the MikroTik RouterOS API for use in Ruby.
|
80
61
|
test_files: []
|
81
|
-
|