mtik 4.0.3 → 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.
Files changed (6) hide show
  1. checksums.yaml +5 -5
  2. data/Rakefile +1 -32
  3. data/VERSION.txt +1 -1
  4. data/lib/mtik.rb +11 -6
  5. data/lib/mtik/connection.rb +101 -35
  6. metadata +5 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0faffc781b693499b3a24a0b07cc918c146903a6
4
- data.tar.gz: 2415ca8ad18b2f83e55ca29e81bfe776f507700c
2
+ SHA256:
3
+ metadata.gz: 6b5f8b42fee32a8aa6397a13fab80370d50af593ec95862dd6d7f2f483a41000
4
+ data.tar.gz: 67856a1a7ffc030967c3ec9ab265c1e1d8189813af6d84b6522fbcc2a397fbdb
5
5
  SHA512:
6
- metadata.gz: 82acc2725c7f074df88f22585a13a1395ffedd4bf9194daa167e893f1d33d11da70bb4a9825c25c294863b7bd88b8bdf3c435d848461862402bf22cf26824e58
7
- data.tar.gz: 04e7aca873f26094fdcd701431bba659ad0663f7528b2ab03ddc9192276cafbda3b80f6d68698d366c9ae74c1049e33602adf8a59b2b625e177f0d2b8c2474ad
6
+ metadata.gz: 81be2978391702f448cc17d10dac4ed7facc7684a4a4270395e7524bc70a41bdfe21d6062a7ec21ca02d1b280352166162c59d099a0e4d1c944e10c68dceafe8
7
+ data.tar.gz: f6e52a8ba3d6abe04553b4631d7bbef9aa43c25a55b2fbfdf961041ffb6dce488af750ecc7320ecef31322e61a5618785b216921b1a5c868354c5fa9735b1c64
data/Rakefile CHANGED
@@ -2,38 +2,7 @@ require 'rubygems'
2
2
  require 'rubygems/package_task'
3
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
7
  Gem::PackageTask.new(gemspec) do |pkg|
39
8
  pkg.need_zip = true
@@ -1 +1 @@
1
- 4.0.3
1
+ 4.0.4
@@ -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:
@@ -56,6 +58,9 @@ module MTik
56
58
  ## Maximum number of replies before a command is auto-canceled:
57
59
  MAXREPLIES = 1000
58
60
 
61
+ ## SSL is set to false by default
62
+ USE_SSL = false
63
+
59
64
  @verbose = false
60
65
  @debug = false
61
66
 
@@ -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,7 +47,8 @@ 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
@@ -54,18 +56,22 @@ class MTik::Connection
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
68
- @os_version = nil
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
69
75
 
70
76
  ## Initiate connection and immediately login to device:
71
77
  login
@@ -95,28 +101,41 @@ class MTik::Connection
95
101
  raise MTik::Error.new("Login failed: Unable to connect to device.")
96
102
  end
97
103
 
98
- ## Send first /login command to obtain the challenge:
99
- reply = get_reply('/login')
100
- ## Make sure the reply has the info we expect:
101
- if reply.length != 1 || reply[0].length != 3 || !reply[0].key?('ret')
102
- 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')
103
115
  end
104
116
 
105
- ## Grab the challenge from first (only) sentence in the reply:
106
- 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
107
122
 
108
- ## Generate reply MD5 hash and convert binary hash to hex string:
109
- 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'])
110
125
 
111
- ## Send second /login command with our response:
112
- reply = get_reply('/login', '=name=' + @user, '=response=00' + response)
113
- if reply[0].key?('!trap')
114
- raise MTik::Error.new("Login failed: " + (reply[0].key?('message') ? reply[0]['message'] : 'Unknown error.'))
115
- end
116
- unless reply.length == 1 && reply[0].length == 2 && reply[0].key?('!done')
117
- @sock.close
118
- @sock = nil
119
- raise MTik::Error.new('Login failed: Unknown response to login.')
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
120
139
  end
121
140
 
122
141
  ## Request the RouterOS version of the device as different versions
@@ -146,6 +165,8 @@ class MTik::Connection
146
165
  end
147
166
  end
148
167
 
168
+ connect_ssl(@sock) if @use_ssl
169
+
149
170
  rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, Errno::ENETUNREACH,
150
171
  Errno::EHOSTUNREACH => e
151
172
  @sock = nil
@@ -153,6 +174,17 @@ class MTik::Connection
153
174
  end
154
175
  end
155
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
+
156
188
  ## Wait for and read exactly one sentence, regardless of content:
157
189
  def get_sentence
158
190
  ## TODO: Implement timeouts, detect disconnection, maybe do auto-reconnect
@@ -206,7 +238,8 @@ class MTik::Connection
206
238
  end
207
239
  oldlen = @data.length
208
240
  ## Read some more data IF any is available:
209
- sel = IO.select([@sock],nil,[@sock], @cmd_timeout)
241
+ sock = @ssl_sock || @sock
242
+ sel = IO.select([sock],nil,[sock], @cmd_timeout)
210
243
  if sel.nil?
211
244
  raise MTik::TimeoutError.new(
212
245
  "Time-out while awaiting data with #{outstanding} pending " +
@@ -214,7 +247,7 @@ class MTik::Connection
214
247
  )
215
248
  end
216
249
  if sel[0].length == 1
217
- @data += @sock.recv(8192)
250
+ @data += recv(8192)
218
251
  elsif sel[2].length == 1
219
252
  raise MTik::Error.new(
220
253
  "I/O (select) error while awaiting data with #{outstanding} pending " +
@@ -384,10 +417,41 @@ class MTik::Connection
384
417
 
385
418
  ## Send the request object over the socket
386
419
  def xmit(req)
387
- @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
+
388
426
  return req
389
427
  end
390
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
+
391
455
  ## Send a command, then wait for the command to complete, then return
392
456
  ## the completed reply.
393
457
  ##
@@ -420,8 +484,10 @@ class MTik::Connection
420
484
 
421
485
  ## Close the connection.
422
486
  def close
423
- return if @sock.nil?
424
- @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
425
491
  @sock = nil
426
492
  end
427
493
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mtik
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.3
4
+ version: 4.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron D. Gifford
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-06-06 00:00:00.000000000 Z
11
+ date: 2019-07-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: MTik implements the MikroTik RouterOS API for use in Ruby.
14
14
  email: email_not_accepted@aarongifford.com
@@ -45,17 +45,16 @@ require_paths:
45
45
  - lib
46
46
  required_ruby_version: !ruby/object:Gem::Requirement
47
47
  requirements:
48
- - - '>='
48
+ - - ">="
49
49
  - !ruby/object:Gem::Version
50
50
  version: '0'
51
51
  required_rubygems_version: !ruby/object:Gem::Requirement
52
52
  requirements:
53
- - - '>='
53
+ - - ">="
54
54
  - !ruby/object:Gem::Version
55
55
  version: '0'
56
56
  requirements: []
57
- rubyforge_project: mtik
58
- rubygems_version: 2.2.1
57
+ rubygems_version: 3.0.4
59
58
  signing_key:
60
59
  specification_version: 4
61
60
  summary: MTik implements the MikroTik RouterOS API for use in Ruby.