
###
#
# This module exposes methods that may be useful to exploits that deal with
# servers that speak Nuuo NUCM protocol for their devices and management software.
#
###
# NUUO Central Management System (NCS)
module Msf
module Exploit::Remote::Nuuo
  #
  # Creates an instance of an Nuuo exploit module.
  #
  def initialize(info = {})
    super(update_info(info,
      'Author'         =>
        [
          'Pedro Ribeiro <pedrib@gmail.com>'
        ],
    ))

    register_options(
      [
        Opt::RHOST,
        Opt::RPORT(5180),
        OptString.new('NCSSESSION', [false, 'Session number of logged in user']),
        OptString.new('NCSUSER', [false, 'NUUO Central Management System username', 'admin']),
        OptString.new('NCSPASS', [false, 'Password for NCSUSER',])
      ], Msf::Exploit::Remote::Nuuo)

    register_advanced_options(
      [
        OptString.new('NCSVERSION', [false, 'Version header used during login']),
        OptBool.new('NCSBRUTEAPI', [false, 'Bruteforce Version header used during login', false]),
        OptBool.new('NCSTRACE', [false, 'Show NCS requests and responses', false])
      ], Msf::Exploit::Remote::Nuuo)
  end

  def connect(global=true)
    c = Rex::Proto::Nuuo::Client.new({
      host: datastore['RHOST'],
      username: datastore['NCSUSER'],
      password: datastore['NCSPASS'],
      user_session: datastore['NCSSESSION'],
      context: { 'Msf' => framework, 'MsfExploit' => self }
    })

    client.close if self.client && global
    self.client = c if global

    c
  end

  def generate_req(opts={})
    case opts['method']
      when 'PING' then client.request_ping(opts)
      when 'SENDLICFILE' then client.request_sendlicfile(opts)
      when 'GETCONFIG' then client.request_getconfig(opts)
      when 'COMMITCONFIG' then client.request_commitconfig(opts)
      when 'USERLOGIN' then client.request_userlogin(opts)
      when 'GETOPENALARM' then client.request_getopenalarm(opts)
      else nil
    end
  end

  def ncs_send_request(opts={}, req=nil, temp: true)
    req = generate_req(opts) unless req
    return nil unless req

    if datastore['NCSTRACE']
      print_status("Request:\r\n#{req.to_s}")
    end

    begin
      conn = temp ? client.connect(temp: temp) : nil
      res = client.send_recv(req, conn)
      if conn && temp
        conn.shutdown
        conn.close
      end

      if datastore['NCSTRACE'] && res
        print_status("Response:\r\n#{res.to_s}")
      end

      res
    rescue ::Errno::EPIPE, ::Timeout::Error => e
      print_line(e.message) if datastore['NCSTRACE']
      nil
    rescue Rex::ConnectionError => e
      vprint_error(e.to_s)
      nil
    rescue ::Exception => e
      print_line(e.message) if datastore['NCSTRACE']
      raise e
    end
  end

  def ncs_login
    unless datastore['NCSVERSION'] || server_version
      if datastore['NCSBRUTEAPI']
        vprint_status('Bruteforcing Version string')
        self.server_version = ncs_version_bruteforce
      else
        print_error('Set NCSBRUTEAPI to bruteforce the Version string or NCSVERSION to set a version string')
        return nil
      end
    end

    self.server_version ||= datastore['NCSVERSION']
    unless server_version
      print_error('Failed to determine server version')
      return nil
    end

    res = ncs_send_request({
      'method'  => 'USERLOGIN',
      'server_version'  => server_version
    }, temp: false)

    if res.headers['User-Session-No']
      self.user_session = res.headers['User-Session-No']
    end

    res
  end

  def ncs_version_bruteforce
    res = ''
    Rex::Proto::Nuuo::Constants::VERSIONS.shuffle.each do |version|
      begin
        res = ncs_send_request({
          'method' => 'USERLOGIN',
          'server_version' => version
        })
      rescue
        print_error('Request failed')
      end

      client.close
      if res && res.headers['User-Session-No']
        vprint_good("Valid version detected: #{version}")
        return version
      end
    end

    return nil
  end

  attr_accessor :client
  attr_accessor :server_version
  attr_accessor :user_session
end
end
