mtik 4.0.1 → 4.1.0

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: 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: []