mtik 4.0.3 → 4.0.4

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