mtik 4.0.1 → 4.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5ce416fc9ce5a6d6768f40c11b68fc983fcf75ba69e10e3bc660a0b405fc1bcc
4
+ data.tar.gz: 910a34bcb91770ea3f5607fd91297fa1d518f0020e35e27e810deb4eb797cd71
5
+ SHA512:
6
+ metadata.gz: 86879188b9b9af32b1d61f61d3c539a3f8482ef1da078f4a347ea548e1480e0c5b6502d73784a7acda610a17d4b6be141dae93a20b07c1c85959aec538834be5
7
+ data.tar.gz: 6778bcf64f479677ccdaec744a8356b936527471870e21aa6878749ceb22d79d43452415113a2e15de4d4be0bbc133d7a32b1d0c7d70807cf7066eb94acab91e
@@ -1,3 +1,41 @@
1
+ 2020-08-23 (23 AUG 2020) VERSION 4.1.0
2
+ * Minor version bump due to changing argument passing for the call to
3
+ MTik.interactive_client()
4
+ * Updated tikcli, tikcommand, and tikfetch commands to add options for
5
+ enabling SSL and/or to use unencrypted plaintext logins (newer API
6
+ login style). Also one can set environment variables MTIK_SSL to
7
+ specify SSL use, or MTIK_UNENCRYPTED_PLAINTEXT to enable unencrypted
8
+ plaintext logins if SSL is NOT used for compatibility with cleartext
9
+ API usage on RouterOS versions 6.43+
10
+ * THANKS to Zdenek Crha (zdenek-crha on github) for pointing out that
11
+ the binary commands were lacking proper argument passing to allow
12
+ for SSL and/or unencrypted plaintext options, and for suggesting the
13
+ use of environment variables as an alterative to CLI options for
14
+ enabling such.
15
+
16
+ 2020-08-22 (22 AUG 2020) VERSION 4.0.5
17
+ * This is a cosmetic version bump for the purpose of updating the gem for wider
18
+ availability via rubygems in addition to directly from github prior to some
19
+ coming feature updates and fixes for newer versions of RouterOS
20
+
21
+ 2019-07-26 (26 JUL 2020) VERSION 4.0.4 Adam Kubica (github user xcr)
22
+ * Adam bumped the version number and separated out a gemspec file
23
+ * Jiacheng (github user krhougs) had submitted a similar update as Adam
24
+
25
+ 2014-02-14 (14 FEB 2014) VERSION 4.0.3 Aaron D. Gifford (http://www.aarongifford.com)
26
+ * Update to fetch() utility, along with some very minor some cosmetic changes
27
+
28
+ 2013-06-06 (06 JUN 2013) VERSION 4.0.2 Aaron D. Gifford (http://www.aarongifford.com)
29
+ Bart Braem (http://www.lalunerouge.net/)
30
+ * Merged Bart Braem's implementation of timeouts and bumped up the version. Thanks, Bart!
31
+ * Updated Rakefile to remove a bit of obsolescence
32
+
33
+ 2012-02-09 (09 FEB 2012) VERSION 4.0.1 Aaron D. Gifford (http://www.aarongifford.com)
34
+ * Added os_version to connections. Upon successful connect and login, the RouterOS
35
+ version is fetched and stored. This will allow future updates to better support
36
+ some commands that differ (like fetch) depending on which RouterOS version is
37
+ installed on the device.
38
+
1
39
  2011-03-25 (25 MAR 2011) VERSION 4.0.0 Aaron D. Gifford (http://www.aarongifford.com)
2
40
  * Per user suggestion, added a new optional cancel parameter to the MTik#command()
3
41
  method that will auto-cancel the supplied command after receiving the specified
@@ -13,6 +51,7 @@
13
51
  to symbol and fixed those, updated error messages to reflect state as a symbol,
14
52
  eliminated a few redundant key?() calls, and fixed a replycounter initialization
15
53
  typo (had set it to 1 instead of 0).
54
+
16
55
  2011-01-11 (11 JAN 2011) VERSION 3.1.2 Aaron D. Gifford (http://www.aarongifford.com)
17
56
  * Added source file encoding comments and updated the copyright notices
18
57
  * Fixed a tiny bug in lib/mtik/connection.rb
@@ -42,7 +81,7 @@
42
81
  * Fixed RDoc formatting in several files, and added an RDocTask to the Rakefile
43
82
 
44
83
  2010-04-23 (23 APR 2010) VERSION 3.0.5 Aaron D. Gifford (http://www.aarongifford.com)
45
- * Double bug-fix (typo fix and logic fix) to request.rb thanks to Allan Eising and
84
+ * Double bug-fix (typo fix and logic fix) to request.rb thanks to Allan Eising and
46
85
  Søren Daugaard. Thank you both for the patch!
47
86
  * Added a brief sanity-check in request.rb to help spotlight logic errors.
48
87
 
@@ -85,3 +124,4 @@
85
124
  fetch.rb
86
125
  * Added VERSION.txt, CHANGELOG.txt, README.txt, LICENSE.txt, and *.gemspec files, moved
87
126
  the example files into the bin subdirectory
127
+
data/README.txt CHANGED
@@ -27,7 +27,7 @@ The latest version of MTik can be found at
27
27
 
28
28
  Ruby RDoc documentation can be found online at
29
29
 
30
- * http://www.aarongifford.com/computers/mtik/latest/doc/
30
+ * http://www.aarongifford.com/computers/mtik/latest/doc/
31
31
 
32
32
  Additional documentation is available at
33
33
 
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
- 4.0.1
1
+ 4.1.0
data/bin/tikcli CHANGED
@@ -42,10 +42,46 @@ $LOAD_PATH.unshift(File.dirname(__FILE__)+'/../lib')
42
42
  require 'rubygems'
43
43
  require 'mtik'
44
44
 
45
- unless ARGV.length == 3
46
- STDERR.print("Usage: #{$0} <host> <user> <pass>\n")
45
+ def usage(msg='')
46
+ STDERR.print(
47
+ (msg.size > 0 ? msg + "\n\n" : '') +
48
+ "Usage: #{$0} [-s|--ssl] [-u|--unencrypted_plaintext] <host> <user> <pass>\n" +
49
+ " --unencrypted_plaintext OR -u - Use the 6.43+ login API even if NOT\n" +
50
+ " using SSL.\n" +
51
+ " --ssl OR -s - Use SSL for the API connection.\n"
52
+ )
47
53
  exit(-1)
48
54
  end
49
55
 
50
- MTik::interactive_client(ARGV[0], ARGV[1], ARGV[2])
56
+ use_ssl = unencrypted_plaintext = false
57
+ while !ARGV[0].nil? && ARGV[0][0] == '-'
58
+ arg = ARGV.shift
59
+ case arg
60
+ when '--ssl', '-s'
61
+ usage("Please do not repeat the --ssl (or -s) parameter") if use_ssl
62
+ use_ssl = true
63
+ when '--unencrypted_plaintext', '-u'
64
+ usage("Please do not repeat the --unencrypted_plaintext (or -u) parameter") if unencrypted_plaintext
65
+ unencrypted_plaintext = true
66
+ else
67
+ usage("Unknown argument #{arg.inspect}")
68
+ end
69
+ end
70
+ usage("Too many arguments.") if ARGV.size > 3
71
+ usage("Insufficient arguments.") if ARGV.size < 3
72
+
73
+ ## Permit setting use_ssl and unencrypted_plaintext via environment variables:
74
+ use_ssl = true if ENV['MTIK_SSL']
75
+ unencrypted_plaintext = true if ENV['MTIK_UNENCRYPTED_PLAINTEXT']
76
+
77
+ args = {
78
+ :host => ARGV[0],
79
+ :user => ARGV[1],
80
+ :pass => ARGV[2],
81
+ :ssl => use_ssl,
82
+ :unencrypted_plaintext => unencrypted_plaintext
83
+ }
84
+ p args
85
+
86
+ MTik::interactive_client(args)
51
87
 
@@ -42,12 +42,39 @@ $LOAD_PATH.unshift(File.dirname(__FILE__)+'/../lib')
42
42
  require 'rubygems'
43
43
  require 'mtik'
44
44
 
45
- unless ARGV.length > 3
46
- STDERR.print("Usage: #{$0} <host> <user> <pass> <command> [<args>...] [<command> [<args> ...]]\n")
45
+ def usage(msg='')
46
+ STDERR.print(
47
+ (msg.size > 0 ? msg + "\n\n" : '') +
48
+ "Usage: #{$0} [-s|--ssl] [-u|--unencrypted_plaintext] <host> <user> <pass> <command> [<args>...] [<command> [<args> ...]]\n" +
49
+ " --unencrypted_plaintext OR -u - Use the 6.43+ login API even if NOT\n" +
50
+ " using SSL.\n" +
51
+ " --ssl OR -s - Use SSL for the API connection.\n"
52
+ )
47
53
  exit(-1)
48
54
  end
49
55
 
50
- MTik::verbose = true ## Set how you want
56
+ MTik::verbose = true ## Set how verbose you want things
57
+
58
+ use_ssl = unencrypted_plaintext = false
59
+ while !ARGV[0].nil? && ARGV[0][0] == '-'
60
+ arg = ARGV.shift
61
+ case arg
62
+ when '--ssl', '-s'
63
+ usage("Please do not repeat the --ssl (or -s) parameter") if use_ssl
64
+ use_ssl = true
65
+ when '--unencrypted_plaintext', '-u'
66
+ usage("Please do not repeat the --unencrypted_plaintext (or -u) parameter") if unencrypted_plaintext
67
+ unencrypted_plaintext = true
68
+ else
69
+ usage("Unknown argument #{arg.inspect}")
70
+ end
71
+ end
72
+ usage("Too few arguments.") if ARGV.size < 4
73
+ usage("First command must start with a slash '/' character. #{ARGV[3].inspect}") if ARGV[3].nil? || ARGV[3][0] != '/'
74
+
75
+ ## Permit setting use_ssl and unencrypted_plaintext via environment variables:
76
+ use_ssl = true if ENV['MTIK_SSL']
77
+ unencrypted_plaintext = true if ENV['MTIK_UNENCRYPTED_PLAINTEXT']
51
78
 
52
79
  ## Detect multiple command sequences and build an array of arrays
53
80
  ## where each outer array element is a command plus arguments:
@@ -62,10 +89,14 @@ while i < ARGV.length
62
89
  i += 1
63
90
  end
64
91
 
65
- p MTik::command(
66
- :host=>ARGV[0],
67
- :user=>ARGV[1],
68
- :pass=>ARGV[2],
69
- :command=>command
70
- )
92
+ args = {
93
+ :host => ARGV[0],
94
+ :user => ARGV[1],
95
+ :pass => ARGV[2],
96
+ :command => command,
97
+ :ssl => use_ssl,
98
+ :unencrypted_plaintext => unencrypted_plaintext
99
+ }
100
+
101
+ p MTik::command(args)
71
102
 
@@ -46,16 +46,48 @@ require 'mtik'
46
46
  ## output of all API interactions:
47
47
  #MTik::verbose = true
48
48
 
49
- if ARGV.length < 4
50
- print "Usage: #{$0} <device> <user> <pass> <url> [<localfilename> [<url> [<localfilename> ... ]]]\n"
51
- exit
49
+ def usage(msg='')
50
+ STDERR.print(
51
+ (msg.size > 0 ? msg + "\n\n" : '') +
52
+ "Usage: #{$0} [-s|--ssl] [-u|--unencrypted_plaintext] <device> <user> <pass> <url> [<localfilename> [<url> [<localfilename> ... ]]]\n" +
53
+ " --unencrypted_plaintext OR -u - Use the 6.43+ login API even if NOT\n" +
54
+ " using SSL.\n" +
55
+ " --ssl OR -s - Use SSL for the API connection.\n"
56
+ )
57
+ exit(-1)
58
+ end
59
+
60
+ use_ssl = unencrypted_plaintext = false
61
+ while !ARGV[0].nil? && ARGV[0][0] == '-'
62
+ arg = ARGV.shift
63
+ case arg
64
+ when '--ssl', '-s'
65
+ usage("Please do not repeat the --ssl (or -s) parameter") if use_ssl
66
+ use_ssl = true
67
+ when '--unencrypted_plaintext', '-u'
68
+ usage("Please do not repeat the --unencrypted_plaintext (or -u) parameter") if unencrypted_plaintext
69
+ unencrypted_plaintext = true
70
+ else
71
+ usage("Unknown argument #{arg.inspect}")
72
+ end
52
73
  end
74
+ usage("Too few arguments.") if ARGV.size < 4
75
+
76
+ ## Permit setting use_ssl and unencrypted_plaintext via environment variables:
77
+ use_ssl = true if ENV['MTIK_SSL']
78
+ unencrypted_plaintext = true if ENV['MTIK_UNENCRYPTED_PLAINTEXT']
79
+
80
+ args = {
81
+ :host => ARGV.shift,
82
+ :user => ARGV.shift,
83
+ :pass => ARGV.shift,
84
+ :ssl => use_ssl,
85
+ :unencrypted_plaintext => unencrypted_plaintext
86
+ }
87
+
53
88
 
54
- host = ARGV.shift
55
- user = ARGV.shift
56
- pass = ARGV.shift
57
89
  begin
58
- mt = MTik::Connection.new(:host=>host, :user=>user, :pass=>pass)
90
+ mt = MTik::Connection.new(args)
59
91
  rescue Errno::ETIMEDOUT, Errno::ENETUNREACH, Errno::EHOSTUNREACH, MTik::Error => e
60
92
  print ">>> ERROR CONNECTING: #{e}\n"
61
93
  exit
@@ -129,6 +161,7 @@ print "SIZE CREATED FILENAME\n"
129
161
  print "====================================================================\n"
130
162
  mt.get_reply_each('/file/getall') do |req, s|
131
163
  unless s.key?('!done')
164
+ s['size'] = 'directory' if s['type'] == 'directory'
132
165
  print "#{(s['size']+' ')[0,10]} #{s['creation-time']} #{s['name']}\n"
133
166
  end
134
167
  end
@@ -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,20 +34,22 @@
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:
49
51
  PASS = ''
50
- ## Connection timeout default -- *NOT USED*
52
+ ## Connection timeout default -- *NOT USED*
51
53
  CONN_TIMEOUT = 60
52
54
  ## Command timeout -- The maximum number of seconds to wait for more
53
55
  ## API data when expecting one or more command responses.
@@ -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
 
@@ -81,12 +86,30 @@ module MTik
81
86
 
82
87
 
83
88
  ## Act as an interactive client with the device, accepting user
84
- ## input from STDIN.
85
- def self.interactive_client(host, user, pass)
89
+ ## input from STDIN. Arguments are key/value pairs, and are
90
+ ## simply passed directly to MTik::Connection(). The below
91
+ ## documentation is taken directly from MTik::Connection. One
92
+ ## more ## key/value pair style arguments must be specified.
93
+ ## The one ## required argument is the host or IP of the device
94
+ ## to connect to.
95
+ ## +host+:: This is the only _required_ argument. Example:
96
+ ## <i> :host => "rb411.example.org" </i>
97
+ ## +ssl+:: Use SSL to encrypt communications
98
+ ## +port+:: Override the default API port (8728/8729)
99
+ ## +user+:: Override the default API username ('admin')
100
+ ## +pass+:: Override the default API password (blank)
101
+ ## +conn_timeout+:: Override the default connection
102
+ ## timeout (60 seconds)
103
+ ## +cmd_timeout+:: Override the default command timeout
104
+ ## (60 seconds) -- the number of seconds
105
+ ## to wait for additional API input.
106
+ ## +unencrypted_plaintext+:: Attempt to use the 6.43+ login API
107
+ ## even without SSL
108
+ def self.interactive_client(args)
86
109
  old_verbose = MTik::verbose
87
110
  MTik::verbose = true
88
111
  begin
89
- tk = MTik::Connection.new(:host => host, :user => user, :pass => pass)
112
+ tk = MTik::Connection.new(args)
90
113
  rescue MTik::Error, Errno::ECONNREFUSED => e
91
114
  print "=== LOGIN ERROR: #{e.message}\n"
92
115
  exit
@@ -127,7 +150,7 @@ module MTik
127
150
  cmd == '/tool/fetch' && sentence['status'] == 'finished'
128
151
  ) || (maxreply > 0 && count == maxreply)
129
152
  state = 2
130
- req.cancel do |r, s|
153
+ req.cancel do |r, s|
131
154
  state = 1
132
155
  end
133
156
  end
@@ -154,7 +177,7 @@ module MTik
154
177
  end
155
178
  end
156
179
  end
157
-
180
+
158
181
  reply = tk.get_reply('/quit')
159
182
  unless reply[0].key?('!fatal')
160
183
  raise MTik::Error.new("Unexpected response to '/quit' command.")
@@ -240,16 +263,9 @@ module MTik
240
263
  ## Remember that the limit applies separately to each API command
241
264
  ## executed.
242
265
  def self.command(args)
243
- tk = MTik::Connection.new(
244
- :host => args[:host],
245
- :user => args[:user],
246
- :pass => args[:pass],
247
- :port => args[:port],
248
- :conn_timeout => args[:conn_timeout],
249
- :cmd_timeout => args[:cmd_timeout]
250
- )
251
- limit = args[:limit] ## Optional reply limit
252
- cmd = args[:command]
266
+ tk = MTik::Connection.new(args)
267
+ limit = args[:limit] ## Optional reply limit
268
+ cmd = args[:command]
253
269
  replies = Array.new
254
270
  if cmd.is_a?(String)
255
271
  ## Single command, no arguments
@@ -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,26 +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
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[:unencrypted_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
@@ -130,16 +149,39 @@ class MTik::Connection
130
149
  ## Connect to the device
131
150
  def connect
132
151
  return unless @sock.nil?
133
- ## TODO: Perhaps catch more errors; implement connection timeout
152
+ ## TODO: Perhaps catch more errors
134
153
  begin
135
- @sock = TCPSocket::new(@host, @port)
136
- rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, Errno::ENETUNREACH,
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
+ raise Errno::ETIMEDOUT unless ready
162
+ end
163
+
164
+ connect_ssl(@sock) if @use_ssl
165
+ rescue Errno::ECONNREFUSED,
166
+ Errno::ETIMEDOUT,
167
+ Errno::ENETUNREACH,
137
168
  Errno::EHOSTUNREACH => e
138
169
  @sock = nil
139
170
  raise e ## Re-raise the exception
140
171
  end
141
172
  end
142
173
 
174
+ def connect_ssl(sock)
175
+ ssl_context = OpenSSL::SSL::SSLContext.new()
176
+ ssl_context.ciphers = ['HIGH']
177
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(sock, ssl_context)
178
+ ssl_socket.sync_close = true
179
+ unless ssl_socket.connect
180
+ raise MTik::Error.new("Cannot establish SSL connection.")
181
+ end
182
+ @ssl_sock = ssl_socket
183
+ end
184
+
143
185
  ## Wait for and read exactly one sentence, regardless of content:
144
186
  def get_sentence
145
187
  ## TODO: Implement timeouts, detect disconnection, maybe do auto-reconnect
@@ -193,7 +235,8 @@ class MTik::Connection
193
235
  end
194
236
  oldlen = @data.length
195
237
  ## Read some more data IF any is available:
196
- sel = IO.select([@sock],nil,[@sock], @cmd_timeout)
238
+ sock = @ssl_sock || @sock
239
+ sel = IO.select([sock],nil,[sock], @cmd_timeout)
197
240
  if sel.nil?
198
241
  raise MTik::TimeoutError.new(
199
242
  "Time-out while awaiting data with #{outstanding} pending " +
@@ -201,7 +244,7 @@ class MTik::Connection
201
244
  )
202
245
  end
203
246
  if sel[0].length == 1
204
- @data += @sock.recv(8192)
247
+ @data += recv(8192)
205
248
  elsif sel[2].length == 1
206
249
  raise MTik::Error.new(
207
250
  "I/O (select) error while awaiting data with #{outstanding} pending " +
@@ -244,7 +287,7 @@ class MTik::Connection
244
287
  sentence = get_sentence ## This call must be ATOMIC or re-entrant safety fails
245
288
 
246
289
  ## Check for '!fatal' before checking for a tag--'!fatal'
247
- ## is never(???) tagged:
290
+ ## is never(???) tagged:
248
291
  if sentence.key?('!fatal')
249
292
  ## FATAL ERROR has occured! (Or a '/quit' command was issued...)
250
293
  if @data.length > 0
@@ -343,7 +386,7 @@ class MTik::Connection
343
386
  ## +args+:: Zero or more arguments to the command
344
387
  ## +callback+:: Proc/lambda code (or code block if not provided as
345
388
  ## an argument) to be called. (See the +await_completion+
346
- ##
389
+ ##
347
390
  def send_request(await_completion, command, *args, &callback)
348
391
  if await_completion.is_a?(MTik::Request)
349
392
  req = await_completion
@@ -371,10 +414,45 @@ class MTik::Connection
371
414
 
372
415
  ## Send the request object over the socket
373
416
  def xmit(req)
374
- @sock.send(req.request, 0)
417
+ begin
418
+ if @ssl_sock
419
+ @ssl_sock.write(req.request)
420
+ else
421
+ @sock.send(req.request, 0)
422
+ end
423
+ rescue Errno::EPIPE => e
424
+ @sock = @ssl_sock = nil
425
+ raise e ## Re-raise the exception
426
+ end
375
427
  return req
376
428
  end
377
429
 
430
+ def recv(buffer_size)
431
+ if @ssl_sock
432
+ recv_openssl(buffer_size)
433
+ else
434
+ @sock.recv(buffer_size)
435
+ end
436
+ end
437
+
438
+ # 2 cases for backwards compatibility
439
+ def recv_openssl(buffer_size)
440
+ if OpenSSL::SSL.const_defined? 'SSLErrorWaitReadable'.freeze
441
+ begin
442
+ @ssl_sock.read_nonblock(buffer_size)
443
+ rescue OpenSSL::SSL::SSLErrorWaitReadable
444
+ ''
445
+ end
446
+ else
447
+ begin
448
+ @ssl_sock.read_nonblock(buffer_size)
449
+ rescue OpenSSL::SSL::SSLError => e
450
+ return '' if e.message == 'read would block'.freeze
451
+ raise e
452
+ end
453
+ end
454
+ end
455
+
378
456
  ## Send a command, then wait for the command to complete, then return
379
457
  ## the completed reply.
380
458
  ##
@@ -407,8 +485,10 @@ class MTik::Connection
407
485
 
408
486
  ## Close the connection.
409
487
  def close
410
- return if @sock.nil?
411
- @sock.close
488
+ return if @sock.nil? and @ssl_sock.nil?
489
+ @ssl_sock.close if @ssl_sock and !@ssl_sock.closed?
490
+ @sock.close if @sock and !@sock.closed?
491
+ @ssl_sock = nil
412
492
  @sock = nil
413
493
  end
414
494
 
@@ -512,16 +592,53 @@ class MTik::Connection
512
592
  ## +total+:: Final expected file size in bytes
513
593
  ## +bytes+:: Number of bytes transferred so far
514
594
  ## +request+:: The MTik::Request object
515
- def fetch(url, filename, timeout=nil, &callback)
595
+ def fetch(url, filename=nil, timeout=nil, &callback)
596
+ require 'uri'
597
+
598
+ uri = URI(url)
599
+ filename = File.basename(uri.path) if filename.nil?
600
+
516
601
  total = bytes = oldbytes = 0
517
602
  status = ''
518
603
  done = false
519
604
  lastactivity = Time.now
520
- req = get_reply_each(
521
- '/tool/fetch',
522
- '=url=' + url,
523
- '=dst-path=' + filename
524
- ) do |r, s|
605
+
606
+ ## RouterOS versions 4.9 and prior (not sure if this version cut-off
607
+ ## is exactly right) would accept the url parameter, but failed to
608
+ ## download the files. So for versions older than this, we'll use
609
+ ## the mode/src-path/port parameters instead if possible.
610
+ if !@os_version.nil? && lambda {|a,b|
611
+ sr = %r{(?:\.|rc|beta|alpha)}
612
+ a = a.split(sr).map{|i| i.to_i}
613
+ b = b.split(sr).map{|i| i.to_i}
614
+ i = 0
615
+ while i < a.size && i < b.size
616
+ return -1 if a[i] < b[i]
617
+ return 1 if a[i] > b[i]
618
+ i += 1
619
+ end
620
+ return a.size <=> b.size
621
+ }.call(@os_version, '4.9') < 1
622
+ command = [
623
+ '/tool/fetch', '=mode=' + uri.scheme,
624
+ '=src-path=' + uri.path + (uri.query.size > 0 ? '?' + uri.query : ''),
625
+ '=dst-path=' + filename
626
+ ]
627
+ case uri.scheme
628
+ when 'http'
629
+ command << '=port=80'
630
+ when 'https'
631
+ command << '=port=443'
632
+ end
633
+ else
634
+ command = [
635
+ '/tool/fetch',
636
+ '=url=' + url,
637
+ '=dst-path=' + filename
638
+ ]
639
+ end
640
+
641
+ req = get_reply_each(command[0], *command[1..-1]) do |r, s|
525
642
  if s.key?('!re') && !done
526
643
  unless s.key?('status')
527
644
  raise MTik::Error.new("Unknown response to '/tool/fetch': missing 'status' in response.")
@@ -611,7 +728,7 @@ class MTik::Connection
611
728
  else
612
729
  raise MTik::Error.new("Invalid settings match class '#{keyitem}' (expected Array, Regexp, or String)")
613
730
  end
614
-
731
+
615
732
  if s.key?(key)
616
733
  ## A key matches! && s[k] != v
617
734
  oldv = s[k]
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mtik
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.1
5
- prerelease:
4
+ version: 4.1.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Aaron D. Gifford
9
- autorequire:
8
+ autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-02-09 00:00:00.000000000 Z
11
+ date: 2020-08-22 00:00:00.000000000 Z
13
12
  dependencies: []
14
13
  description: MTik implements the MikroTik RouterOS API for use in Ruby.
15
14
  email: email_not_accepted@aarongifford.com
@@ -24,12 +23,12 @@ files:
24
23
  - CHANGELOG.txt
25
24
  - LICENSE.txt
26
25
  - README.txt
27
- - VERSION.txt
28
26
  - Rakefile
29
- - examples/tikjson.rb
27
+ - VERSION.txt
30
28
  - bin/tikcli
31
29
  - bin/tikcommand
32
30
  - bin/tikfetch
31
+ - examples/tikjson.rb
33
32
  - lib/mtik.rb
34
33
  - lib/mtik/connection.rb
35
34
  - lib/mtik/error.rb
@@ -39,26 +38,24 @@ files:
39
38
  - lib/mtik/timeouterror.rb
40
39
  homepage: http://www.aarongifford.com/computers/mtik/
41
40
  licenses: []
42
- post_install_message:
41
+ metadata: {}
42
+ post_install_message:
43
43
  rdoc_options: []
44
44
  require_paths:
45
45
  - lib
46
46
  required_ruby_version: !ruby/object:Gem::Requirement
47
- none: false
48
47
  requirements:
49
- - - ! '>='
48
+ - - ">="
50
49
  - !ruby/object:Gem::Version
51
50
  version: '0'
52
51
  required_rubygems_version: !ruby/object:Gem::Requirement
53
- none: false
54
52
  requirements:
55
- - - ! '>='
53
+ - - ">="
56
54
  - !ruby/object:Gem::Version
57
55
  version: '0'
58
56
  requirements: []
59
- rubyforge_project: mtik
60
- rubygems_version: 1.8.11
61
- signing_key:
62
- specification_version: 3
57
+ rubygems_version: 3.0.6
58
+ signing_key:
59
+ specification_version: 4
63
60
  summary: MTik implements the MikroTik RouterOS API for use in Ruby.
64
61
  test_files: []