# -*- coding: binary -*-
###
#
# This module provides methods for communicating with a host running oracle.
# Dependencies:
#       - Oracle Instant Client
#       - ruby-oci8
#
# Rory McCune <rorym@nmrconsult.net>
# MC <mc@metasploit.com>
#
###


module Msf
module Exploit::ORACLE


  def initialize(info = {})
    super

    register_options(
      [
        OptString.new('RHOST',  [ true, 'The Oracle host.',                   '']),
        OptPort.new('RPORT',  [ true, 'The TNS port.',                      1521]),
        OptString.new('SID',    [ true, 'The sid to authenticate with.',      'ORCL']),
        OptString.new('DBUSER', [ true, 'The username to authenticate with.', 'SCOTT']),
        OptString.new('DBPASS', [ true, 'The password to authenticate with.', 'TIGER']),
      ], Msf::Exploit::ORACLE
    )

    begin
      olang = ENV['NLS_LANG']
      ENV['NLS_LANG'] = 'AMERICAN_AMERICA.WE8ISO8859P1'
      require 'oci8'
      ENV['NLS_LANG'] = olang
      @oci8_loaded = true
    rescue ::Exception => e
      @oci8_loaded = false
      @oci8_error  = e
    end
  end

  def check_dependencies
    if not @oci8_loaded
      print_error("Failed to load the OCI library: #{@oci8_error}")
      print_error("Try 'gem install ruby-oci8'")
      return false
    end
    return true
  end

  def connect
    handle = nil

    if(not @oci8_loaded)
      raise RuntimeError, "Could not load the Oracle driver (oci8): #{@oci8_error}"
    end

    # Create a Connection to the Database
    if datastore['DBUSER'] == 'SYS' || datastore['DBUSER'] == 'SYSTEM'
      begin
        handle = OCI8.new(
          datastore['DBUSER'],
          datastore['DBPASS'],
          "//#{datastore['RHOST']}:#{datastore['RPORT']}/#{datastore['SID']}",
          :SYSDBA
        )
      rescue ::OCIError
        # Try again without a request for SYSDBA
        vprint_status('Insufficient privileges, trying without SYSDBA')
        handle = OCI8.new(
          datastore['DBUSER'],
          datastore['DBPASS'],
          "//#{datastore['RHOST']}:#{datastore['RPORT']}/#{datastore['SID']}"
        )
      end
    else
      handle = OCI8.new(
        datastore['DBUSER'],
        datastore['DBPASS'],
        "//#{datastore['RHOST']}:#{datastore['RPORT']}/#{datastore['SID']}"
      )
    end
    handle
  end

  def disconnect
    connect.logoff
  end

  def prepare_exec(sql)
    begin
      sploit = connect.parse(sql)
    rescue ::OCIError => e
      print_error("#{e.to_s}")
      return
    end
    # DEBUG
    # print_status("did the parse sploit type is " + sploit.type.to_s)
    begin
      sploit.exec
    rescue ::OCIError => e
      if ( e.to_s =~ /ORA-00942: table or view does not exist/ )
        print_status("ORA-00942: table or view does not exist")
        raise RuntimeError, "ORA-00942: table or view does not exist"
      end
    print_status e.to_s
    end

    # The Handling is a little different for certain types of query
    # Mainly Select needs a fetch statement to get the data
    # Also return types are a little different (some return rows changed so we can used that)
    # The case statement could probably be collapsed a bit but leaving it as is for the moment
    # in case it's useful later...

    # Select Queries
    case sploit.type
    when 1, :select_stmt
      # Create an array to return to the calling function
      results = Array.new
      while r = sploit.fetch()
        str = r.join(',')
        # Removed this as it should really be down to the exploit to decide what to print
        # eg leaving this in messes up oraenum.
        # print_status(str)
        results << str
      end

      return results

    # Update Queries
    when 2, :update_stmt
      connect.commit

      # If we were successful our return should be a Integer with the number of rows updated
      result = ['UPDATE Successful ' + sploit.row_count.to_s + ' Rows Updated']
      return result
    # Delete Queries
    when 3, :delete_stmt
      connect.commit
      # If we were successful our return should be a Integer with the number of rows updated
      result = ['DELETE Successful ' + sploit.row_count.to_s + ' Rows Deleted']
      return result
    # Insert Queries
    when 4, :insert_stmt
      connect.commit
      # If we were successful our return should be a Integer with the number of rows updated
      result = ['INSERT Successful ' + sploit.row_count.to_s + ' Rows Inserted']
      return result
    # Create Queries
    when 5, :create_stmt
      connect.commit
      if sploit
        print_status('CREATE successful')
      end
    when 6, :drop_stmt
      connect.commit
      if sploit
        print_status('DROP successful')
      end
    when 7, :alter_stmt
      connect.commit
      if sploit
        print_status('Alter successful')
      end
    when 8, :begin_stmt
      connect.commit
    when 9, :declare_stmt
      connect.commit
    else
      print_status("Didn't match Query Type!")
      print_status("Query type passed was " + sploit.type.to_s)
    end
  end

end
end

