mtik 3.1.2 → 4.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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 cancelled. This should help with
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
@@ -47,7 +47,7 @@ For documentation on the MikroTik RouterOS APi, see
47
47
 
48
48
  To install MTik is through its GEM file:
49
49
 
50
- % [sudo] gem install mtik-3.1.0.gem
50
+ % [sudo] gem install mtik-4.0.0.gem
51
51
 
52
52
  == License
53
53
 
data/Rakefile CHANGED
@@ -1,46 +1,15 @@
1
1
  require 'rubygems'
2
- require 'rake/gempackagetask'
3
- require 'rake/rdoctask'
2
+ require 'rubygems/package_task'
3
+ require 'rdoc/task'
4
4
 
5
- gemspec = Gem::Specification.new do |spec|
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
- Rake::GemPackageTask.new(gemspec) do |pkg|
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
- Rake::RDocTask.new do |rdoc|
12
+ RDoc::Task.new do |rdoc|
44
13
  rdoc.name = 'rdoc'
45
14
  rdoc.main = 'README.txt'
46
15
  rdoc.rdoc_dir = 'doc'
@@ -1 +1 @@
1
- 3.1.2
1
+ 4.0.4
@@ -2,11 +2,13 @@
2
2
  ########################################################################
3
3
  #--
4
4
  #
5
- # FILE: json.rb -- Example of using the Ruby MikroTik API in Ruby
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-2011, InfoWest, Inc.
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
@@ -34,15 +34,17 @@
34
34
  # encoding: ASCII-8BIT
35
35
 
36
36
  module MTik
37
- require 'mtik/error.rb'
38
- require 'mtik/fatalerror.rb'
39
- require 'mtik/timeouterror.rb'
40
- require 'mtik/request.rb'
41
- require 'mtik/reply.rb'
42
- require 'mtik/connection.rb'
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 != 'cancelled' && (
124
- cmd == '/tool/fetch' && sentence.key?('status') && sentence['status'] == 'finished'
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
- ## *WARNING*:
194
- ## If you use this call with an API command like <i>"/tool/fetch"</i>
195
- ## or a similar command that generates replies until canceled,
196
- ## it will forever keep reading replies, never returning. So do
197
- ## _NOT_ use this with any API command that does not complete with
198
- ## a <i>"!done"</i> response without any additional interaction.
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 = 1
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 replycount >= 1000 && req.state != 'cancelled'
233
- ## Auto-cancel any command after 1,000 replies:
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
@@ -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
- ## +port+:: Override the default API port (8728)
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) -- *NOT USED*
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 = nil
59
- @requests = Hash.new
60
- @host = args[:host]
61
- @port = args[:port] || MTik::PORT
62
- @user = args[:user] || MTik::USER
63
- @pass = args[:pass] || MTik::PASS
64
- @conn_timeout = args[:conn_timeout] || MTik::CONN_TIMEOUT
65
- @cmd_timeout = args[:cmd_timeout] || MTik::CMD_TIMEOUT
66
- @data = ''
67
- @parsing = false ## Recursion flag
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
- ## Send first /login command to obtain the challenge:
97
- reply = get_reply('/login')
98
- ## Make sure the reply has the info we expect:
99
- if reply.length != 1 || reply[0].length != 3 || !reply[0].key?('ret')
100
- raise MTik::Error.new("Login failed: unexpected reply to login attempt.")
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
- ## Grab the challenge from first (only) sentence in the reply:
104
- challenge = hex2bin(reply[0]['ret'])
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
- ## Generate reply MD5 hash and convert binary hash to hex string:
107
- response = Digest::MD5.hexdigest(0.chr + @pass + challenge)
123
+ ## Grab the challenge from first (only) sentence in the reply:
124
+ challenge = hex2bin(reply[0]['ret'])
108
125
 
109
- ## Send second /login command with our response:
110
- reply = get_reply('/login', '=name=' + @user, '=response=00' + response)
111
- if reply[0].key?('!trap')
112
- raise MTik::Error.new("Login failed: " + (reply[0].key?('message') ? reply[0]['message'] : 'Unknown error.'))
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
- unless reply.length == 1 && reply[0].length == 2 && reply[0].key?('!done')
115
- @sock.close
116
- @sock = nil
117
- raise MTik::Error.new('Login failed: Unknown response to login.')
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; implement connection timeout
152
+ ## TODO: Perhaps catch more errors
125
153
  begin
126
- @sock = TCPSocket::new(@host, @port)
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
- sel = IO.select([@sock],nil,[@sock], @cmd_timeout)
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 += @sock.recv(8192)
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
- @sock.send(req.request, 0)
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
- @sock.close
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
- req = get_reply_each(
512
- '/tool/fetch',
513
- '=url=' + url,
514
- '=dst-path=' + filename
515
- ) do |r, s|
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]
@@ -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 'sent')"
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 'sent')"
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
- prerelease: false
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
- - examples/tikjson.rb
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
- none: false
59
- requirements:
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
60
48
  - - ">="
61
- - !ruby/object:Gem::Version
62
- segments:
63
- - 0
64
- version: "0"
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
- segments:
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: 3
59
+ specification_version: 4
79
60
  summary: MTik implements the MikroTik RouterOS API for use in Ruby.
80
61
  test_files: []
81
-