# -*- 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>
#
###

require 'msf/core'

module Msf
module Exploit::ORACLE


	def initialize(info = {})
		super

		register_options(
			[
				OptString.new('RHOST',  [ true, 'The Oracle host.',                   '']),
				OptString.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("See http://www.metasploit.com/redmine/projects/framework/wiki/OracleUsage for installation instructions")
			return false
		end
		return true
	end

	def connect
		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'
			handle = OCI8.new(
				datastore['DBUSER'],
				datastore['DBPASS'],
				"//#{datastore['RHOST']}:#{datastore['RPORT']}/#{datastore['SID']}",
				:SYSDBA
			)
		else
			handle = OCI8.new(
				datastore['DBUSER'],
				datastore['DBPASS'],
				"//#{datastore['RHOST']}:#{datastore['RPORT']}/#{datastore['SID']}"
			)
		end

	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 probaby 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 Fixnum 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 Fixnum 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 Fixnum 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

