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.
- checksums.yaml +5 -5
- data/Rakefile +1 -32
- data/VERSION.txt +1 -1
- data/lib/mtik.rb +11 -6
- data/lib/mtik/connection.rb +101 -35
- metadata +5 -6
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 | 
            -
             | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 2 | 
            +
            SHA256:
         | 
| 3 | 
            +
              metadata.gz: 6b5f8b42fee32a8aa6397a13fab80370d50af593ec95862dd6d7f2f483a41000
         | 
| 4 | 
            +
              data.tar.gz: 67856a1a7ffc030967c3ec9ab265c1e1d8189813af6d84b6522fbcc2a397fbdb
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 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. | 
| 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
         | 
    
        data/VERSION.txt
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            4.0. | 
| 1 | 
            +
            4.0.4
         | 
    
        data/lib/mtik.rb
    CHANGED
    
    | @@ -34,15 +34,17 @@ | |
| 34 34 | 
             
            # encoding: ASCII-8BIT
         | 
| 35 35 |  | 
| 36 36 | 
             
            module MTik
         | 
| 37 | 
            -
               | 
| 38 | 
            -
               | 
| 39 | 
            -
               | 
| 40 | 
            -
               | 
| 41 | 
            -
               | 
| 42 | 
            -
               | 
| 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 |  | 
    
        data/lib/mtik/connection.rb
    CHANGED
    
    | @@ -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 | 
            -
              ## + | 
| 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 | 
| 59 | 
            -
                @ | 
| 60 | 
            -
                @ | 
| 61 | 
            -
                @ | 
| 62 | 
            -
                @ | 
| 63 | 
            -
                @ | 
| 64 | 
            -
                @ | 
| 65 | 
            -
                @ | 
| 66 | 
            -
                @ | 
| 67 | 
            -
                @ | 
| 68 | 
            -
                @ | 
| 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 | 
            -
                 | 
| 99 | 
            -
                 | 
| 100 | 
            -
                 | 
| 101 | 
            -
             | 
| 102 | 
            -
                   | 
| 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 | 
            -
                 | 
| 106 | 
            -
             | 
| 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 | 
            -
             | 
| 109 | 
            -
             | 
| 123 | 
            +
                  ## Grab the challenge from first (only) sentence in the reply:
         | 
| 124 | 
            +
                  challenge = hex2bin(reply[0]['ret'])
         | 
| 110 125 |  | 
| 111 | 
            -
             | 
| 112 | 
            -
             | 
| 113 | 
            -
             | 
| 114 | 
            -
                   | 
| 115 | 
            -
             | 
| 116 | 
            -
             | 
| 117 | 
            -
             | 
| 118 | 
            -
                   | 
| 119 | 
            -
                   | 
| 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 | 
            -
                   | 
| 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 +=  | 
| 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 | 
            -
                @ | 
| 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 | 
            -
                @ | 
| 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. | 
| 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:  | 
| 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 | 
            -
             | 
| 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.
         |