See #730. Forking and adding Postgres-PR, with the following changes:
Namespaced everything under Msf::Db::PostgreSQL, renamed top-level include to postgres_msf to disambiguate. Included recursive requires for all files. Noted the IO monkeypatch -- should revisit. Added a testcase for database connections. The reason for the namespacing is to avoid stomping on any existing Postgres-PR installations, or any other requires named "postgres" or "postgresql" or even "pg," since these may or may not support the method's we're using here. The seperate namespace also allows for easier integration of custom commands later on. git-svn-id: file:///home/svn/framework3/trunk@8342 4d416f70-5f16-0410-b530-b9f4589650daunstable
parent
554f46be43
commit
a65af9c8b3
|
@ -0,0 +1,128 @@
|
|||
require 'postgres_msf'
|
||||
require 'postgres/byteorder'
|
||||
|
||||
# Namespace for Metasploit branch.
|
||||
module Msf
|
||||
module Db
|
||||
|
||||
# This mixin solely depends on method read(n), which must be defined
|
||||
# in the class/module where you mixin this module.
|
||||
module BinaryReaderMixin
|
||||
|
||||
# == 8 bit
|
||||
|
||||
# no byteorder for 8 bit!
|
||||
|
||||
def read_word8
|
||||
ru(1, 'C')
|
||||
end
|
||||
|
||||
def read_int8
|
||||
ru(1, 'c')
|
||||
end
|
||||
|
||||
alias read_byte read_word8
|
||||
|
||||
# == 16 bit
|
||||
|
||||
# === Unsigned
|
||||
|
||||
def read_word16_native
|
||||
ru(2, 'S')
|
||||
end
|
||||
|
||||
def read_word16_little
|
||||
ru(2, 'v')
|
||||
end
|
||||
|
||||
def read_word16_big
|
||||
ru(2, 'n')
|
||||
end
|
||||
|
||||
# === Signed
|
||||
|
||||
def read_int16_native
|
||||
ru(2, 's')
|
||||
end
|
||||
|
||||
def read_int16_little
|
||||
# swap bytes if native=big (but we want little)
|
||||
ru_swap(2, 's', ByteOrder::Big)
|
||||
end
|
||||
|
||||
def read_int16_big
|
||||
# swap bytes if native=little (but we want big)
|
||||
ru_swap(2, 's', ByteOrder::Little)
|
||||
end
|
||||
|
||||
# == 32 bit
|
||||
|
||||
# === Unsigned
|
||||
|
||||
def read_word32_native
|
||||
ru(4, 'L')
|
||||
end
|
||||
|
||||
def read_word32_little
|
||||
ru(4, 'V')
|
||||
end
|
||||
|
||||
def read_word32_big
|
||||
ru(4, 'N')
|
||||
end
|
||||
|
||||
# === Signed
|
||||
|
||||
def read_int32_native
|
||||
ru(4, 'l')
|
||||
end
|
||||
|
||||
def read_int32_little
|
||||
# swap bytes if native=big (but we want little)
|
||||
ru_swap(4, 'l', ByteOrder::Big)
|
||||
end
|
||||
|
||||
def read_int32_big
|
||||
# swap bytes if native=little (but we want big)
|
||||
ru_swap(4, 'l', ByteOrder::Little)
|
||||
end
|
||||
|
||||
# == Aliases
|
||||
|
||||
alias read_uint8 read_word8
|
||||
|
||||
# add some short-cut functions
|
||||
%w(word16 int16 word32 int32).each do |typ|
|
||||
alias_method "read_#{typ}_network", "read_#{typ}_big"
|
||||
end
|
||||
|
||||
{:word16 => :uint16, :word32 => :uint32}.each do |old, new|
|
||||
['_native', '_little', '_big', '_network'].each do |bo|
|
||||
alias_method "read_#{new}#{bo}", "read_#{old}#{bo}"
|
||||
end
|
||||
end
|
||||
|
||||
# read exactly n characters, otherwise raise an exception.
|
||||
def readn(n)
|
||||
str = read(n)
|
||||
raise "couldn't read #{n} characters" if str.nil? or str.size != n
|
||||
str
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# shortcut method for readn+unpack
|
||||
def ru(size, template)
|
||||
readn(size).unpack(template).first
|
||||
end
|
||||
|
||||
# same as method +ru+, but swap bytes if native byteorder == _byteorder_
|
||||
def ru_swap(size, template, byteorder)
|
||||
str = readn(size)
|
||||
str.reverse! if ByteOrder.byteorder == byteorder
|
||||
str.unpack(template).first
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,108 @@
|
|||
require 'postgres_msf'
|
||||
require 'postgres/byteorder'
|
||||
|
||||
# Namespace for Metasploit branch.
|
||||
module Msf
|
||||
module Db
|
||||
|
||||
module BinaryWriterMixin
|
||||
|
||||
# == 8 bit
|
||||
|
||||
# no byteorder for 8 bit!
|
||||
|
||||
def write_word8(val)
|
||||
pw(val, 'C')
|
||||
end
|
||||
|
||||
def write_int8(val)
|
||||
pw(val, 'c')
|
||||
end
|
||||
|
||||
alias write_byte write_word8
|
||||
|
||||
# == 16 bit
|
||||
|
||||
# === Unsigned
|
||||
|
||||
def write_word16_native(val)
|
||||
pw(val, 'S')
|
||||
end
|
||||
|
||||
def write_word16_little(val)
|
||||
str = [val].pack('S')
|
||||
str.reverse! if ByteOrder.network? # swap bytes as native=network (and we want little)
|
||||
write(str)
|
||||
end
|
||||
|
||||
def write_word16_network(val)
|
||||
str = [val].pack('S')
|
||||
str.reverse! if ByteOrder.little? # swap bytes as native=little (and we want network)
|
||||
write(str)
|
||||
end
|
||||
|
||||
# === Signed
|
||||
|
||||
def write_int16_native(val)
|
||||
pw(val, 's')
|
||||
end
|
||||
|
||||
def write_int16_little(val)
|
||||
pw(val, 'v')
|
||||
end
|
||||
|
||||
def write_int16_network(val)
|
||||
pw(val, 'n')
|
||||
end
|
||||
|
||||
# == 32 bit
|
||||
|
||||
# === Unsigned
|
||||
|
||||
def write_word32_native(val)
|
||||
pw(val, 'L')
|
||||
end
|
||||
|
||||
def write_word32_little(val)
|
||||
str = [val].pack('L')
|
||||
str.reverse! if ByteOrder.network? # swap bytes as native=network (and we want little)
|
||||
write(str)
|
||||
end
|
||||
|
||||
def write_word32_network(val)
|
||||
str = [val].pack('L')
|
||||
str.reverse! if ByteOrder.little? # swap bytes as native=little (and we want network)
|
||||
write(str)
|
||||
end
|
||||
|
||||
# === Signed
|
||||
|
||||
def write_int32_native(val)
|
||||
pw(val, 'l')
|
||||
end
|
||||
|
||||
def write_int32_little(val)
|
||||
pw(val, 'V')
|
||||
end
|
||||
|
||||
def write_int32_network(val)
|
||||
pw(val, 'N')
|
||||
end
|
||||
|
||||
# add some short-cut functions
|
||||
%w(word16 int16 word32 int32).each do |typ|
|
||||
alias_method "write_#{typ}_big", "write_#{typ}_network"
|
||||
end
|
||||
|
||||
# == Other methods
|
||||
|
||||
private
|
||||
|
||||
# shortcut for pack and write
|
||||
def pw(val, template)
|
||||
write([val].pack(template))
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,105 @@
|
|||
require 'postgres_msf'
|
||||
require 'postgres/binary_writer'
|
||||
require 'postgres/binary_reader'
|
||||
|
||||
# Namespace for Metasploit branch.
|
||||
module Msf
|
||||
module Db
|
||||
|
||||
# Fixed size buffer.
|
||||
class Buffer
|
||||
|
||||
class Error < RuntimeError; end
|
||||
class EOF < Error; end
|
||||
|
||||
def self.from_string(str)
|
||||
new(str)
|
||||
end
|
||||
|
||||
def self.of_size(size)
|
||||
raise ArgumentError if size < 0
|
||||
new('#' * size)
|
||||
end
|
||||
|
||||
def initialize(content)
|
||||
@size = content.size
|
||||
@content = content
|
||||
@position = 0
|
||||
end
|
||||
|
||||
def size
|
||||
@size
|
||||
end
|
||||
|
||||
def position
|
||||
@position
|
||||
end
|
||||
|
||||
def position=(new_pos)
|
||||
raise ArgumentError if new_pos < 0 or new_pos > @size
|
||||
@position = new_pos
|
||||
end
|
||||
|
||||
def at_end?
|
||||
@position == @size
|
||||
end
|
||||
|
||||
def content
|
||||
@content
|
||||
end
|
||||
|
||||
def read(n)
|
||||
raise EOF, 'cannot read beyond the end of buffer' if @position + n > @size
|
||||
str = @content[@position, n]
|
||||
@position += n
|
||||
str
|
||||
end
|
||||
|
||||
def write(str)
|
||||
sz = str.size
|
||||
raise EOF, 'cannot write beyond the end of buffer' if @position + sz > @size
|
||||
@content[@position, sz] = str
|
||||
@position += sz
|
||||
self
|
||||
end
|
||||
|
||||
def copy_from_stream(stream, n)
|
||||
raise ArgumentError if n < 0
|
||||
while n > 0
|
||||
str = stream.read(n)
|
||||
write(str)
|
||||
n -= str.size
|
||||
end
|
||||
raise if n < 0
|
||||
end
|
||||
|
||||
NUL = "\000"
|
||||
|
||||
def write_cstring(cstr)
|
||||
raise ArgumentError, "Invalid Ruby/cstring" if cstr.include?(NUL)
|
||||
write(cstr)
|
||||
write(NUL)
|
||||
end
|
||||
|
||||
# returns a Ruby string without the trailing NUL character
|
||||
def read_cstring
|
||||
nul_pos = @content.index(NUL, @position)
|
||||
raise Error, "no cstring found!" unless nul_pos
|
||||
|
||||
sz = nul_pos - @position
|
||||
str = @content[@position, sz]
|
||||
@position += sz + 1
|
||||
return str
|
||||
end
|
||||
|
||||
# read till the end of the buffer
|
||||
def read_rest
|
||||
read(self.size-@position)
|
||||
end
|
||||
|
||||
include BinaryWriterMixin
|
||||
include BinaryReaderMixin
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,41 @@
|
|||
require 'postgres_msf'
|
||||
|
||||
# Namespace for Metasploit branch.
|
||||
module Msf
|
||||
module Db
|
||||
|
||||
module ByteOrder
|
||||
Native = :Native
|
||||
BigEndian = Big = Network = :BigEndian
|
||||
LittleEndian = Little = :LittleEndian
|
||||
|
||||
# examines the byte order of the underlying machine
|
||||
def byte_order
|
||||
if [0x12345678].pack("L") == "\x12\x34\x56\x78"
|
||||
BigEndian
|
||||
else
|
||||
LittleEndian
|
||||
end
|
||||
end
|
||||
|
||||
alias byteorder byte_order
|
||||
|
||||
def little_endian?
|
||||
byte_order == LittleEndian
|
||||
end
|
||||
|
||||
def big_endian?
|
||||
byte_order == BigEndian
|
||||
end
|
||||
|
||||
alias little? little_endian?
|
||||
alias big? big_endian?
|
||||
alias network? big_endian?
|
||||
|
||||
module_function :byte_order, :byteorder
|
||||
module_function :little_endian?, :little?
|
||||
module_function :big_endian?, :big?, :network?
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,182 @@
|
|||
#
|
||||
# Author:: Michael Neumann
|
||||
# Copyright:: (c) 2005 by Michael Neumann
|
||||
# License:: Same as Ruby's or BSD
|
||||
#
|
||||
|
||||
require 'postgres_msf'
|
||||
require 'postgres/postgres-pr/message'
|
||||
require 'postgres/postgres-pr/version'
|
||||
require 'uri'
|
||||
require 'socket'
|
||||
|
||||
# Namespace for Metasploit branch.
|
||||
module Msf
|
||||
module Db
|
||||
|
||||
module PostgresPR
|
||||
|
||||
PROTO_VERSION = 3 << 16 #196608
|
||||
|
||||
class Connection
|
||||
|
||||
# A block which is called with the NoticeResponse object as parameter.
|
||||
attr_accessor :notice_processor
|
||||
|
||||
#
|
||||
# Returns one of the following statuses:
|
||||
#
|
||||
# PQTRANS_IDLE = 0 (connection idle)
|
||||
# PQTRANS_INTRANS = 2 (idle, within transaction block)
|
||||
# PQTRANS_INERROR = 3 (idle, within failed transaction)
|
||||
# PQTRANS_UNKNOWN = 4 (cannot determine status)
|
||||
#
|
||||
# Not yet implemented is:
|
||||
#
|
||||
# PQTRANS_ACTIVE = 1 (command in progress)
|
||||
#
|
||||
def transaction_status
|
||||
case @transaction_status
|
||||
when ?I
|
||||
0
|
||||
when ?T
|
||||
2
|
||||
when ?E
|
||||
3
|
||||
else
|
||||
4
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(database, user, password=nil, uri = nil)
|
||||
uri ||= DEFAULT_URI
|
||||
|
||||
@transaction_status = nil
|
||||
@params = {}
|
||||
establish_connection(uri)
|
||||
|
||||
@conn << StartupMessage.new(PROTO_VERSION, 'user' => user, 'database' => database).dump
|
||||
|
||||
loop do
|
||||
msg = Message.read(@conn)
|
||||
|
||||
case msg
|
||||
when AuthentificationClearTextPassword
|
||||
raise ArgumentError, "no password specified" if password.nil?
|
||||
@conn << PasswordMessage.new(password).dump
|
||||
|
||||
when AuthentificationCryptPassword
|
||||
raise ArgumentError, "no password specified" if password.nil?
|
||||
@conn << PasswordMessage.new(password.crypt(msg.salt)).dump
|
||||
|
||||
when AuthentificationMD5Password
|
||||
raise ArgumentError, "no password specified" if password.nil?
|
||||
require 'digest/md5'
|
||||
|
||||
m = Digest::MD5.hexdigest(password + user)
|
||||
m = Digest::MD5.hexdigest(m + msg.salt)
|
||||
m = 'md5' + m
|
||||
@conn << PasswordMessage.new(m).dump
|
||||
|
||||
when AuthentificationKerberosV4, AuthentificationKerberosV5, AuthentificationSCMCredential
|
||||
raise "unsupported authentification"
|
||||
|
||||
when AuthentificationOk
|
||||
when ErrorResponse
|
||||
raise msg.field_values.join("\t")
|
||||
when NoticeResponse
|
||||
@notice_processor.call(msg) if @notice_processor
|
||||
when ParameterStatus
|
||||
@params[msg.key] = msg.value
|
||||
when BackendKeyData
|
||||
# TODO
|
||||
#p msg
|
||||
when ReadyForQuery
|
||||
@transaction_status = msg.backend_transaction_status_indicator
|
||||
break
|
||||
else
|
||||
raise "unhandled message type"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def close
|
||||
raise "connection already closed" if @conn.nil?
|
||||
@conn.shutdown
|
||||
@conn = nil
|
||||
end
|
||||
|
||||
class Result
|
||||
attr_accessor :rows, :fields, :cmd_tag
|
||||
def initialize(rows=[], fields=[])
|
||||
@rows, @fields = rows, fields
|
||||
end
|
||||
end
|
||||
|
||||
def query(sql)
|
||||
@conn << Query.dump(sql)
|
||||
|
||||
result = Result.new
|
||||
errors = []
|
||||
|
||||
loop do
|
||||
msg = Message.read(@conn)
|
||||
case msg
|
||||
when DataRow
|
||||
result.rows << msg.columns
|
||||
when CommandComplete
|
||||
result.cmd_tag = msg.cmd_tag
|
||||
when ReadyForQuery
|
||||
@transaction_status = msg.backend_transaction_status_indicator
|
||||
break
|
||||
when RowDescription
|
||||
result.fields = msg.fields
|
||||
when CopyInResponse
|
||||
when CopyOutResponse
|
||||
when EmptyQueryResponse
|
||||
when ErrorResponse
|
||||
# TODO
|
||||
errors << msg
|
||||
when NoticeResponse
|
||||
@notice_processor.call(msg) if @notice_processor
|
||||
else
|
||||
# TODO
|
||||
end
|
||||
end
|
||||
|
||||
raise errors.map{|e| e.field_values.join("\t") }.join("\n") unless errors.empty?
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
DEFAULT_PORT = 5432
|
||||
DEFAULT_HOST = 'localhost'
|
||||
DEFAULT_PATH = '/tmp'
|
||||
DEFAULT_URI =
|
||||
if RUBY_PLATFORM.include?('win')
|
||||
'tcp://' + DEFAULT_HOST + ':' + DEFAULT_PORT.to_s
|
||||
else
|
||||
'unix:' + File.join(DEFAULT_PATH, '.s.PGSQL.' + DEFAULT_PORT.to_s)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# tcp://localhost:5432
|
||||
# unix:/tmp/.s.PGSQL.5432
|
||||
def establish_connection(uri)
|
||||
u = URI.parse(uri)
|
||||
case u.scheme
|
||||
when 'tcp'
|
||||
@conn = TCPSocket.new(u.host || DEFAULT_HOST, u.port || DEFAULT_PORT)
|
||||
when 'unix'
|
||||
@conn = UNIXSocket.new(u.path)
|
||||
else
|
||||
raise 'unrecognized uri scheme format (must be tcp or unix)'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end # module PostgresPR
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,552 @@
|
|||
#
|
||||
# Author:: Michael Neumann
|
||||
# Copyright:: (c) 2005 by Michael Neumann
|
||||
# License:: Same as Ruby's or BSD
|
||||
#
|
||||
|
||||
require 'postgres_msf'
|
||||
require 'postgres/buffer'
|
||||
|
||||
# TODO: Revisit this monkeypatch.
|
||||
class IO
|
||||
def read_exactly_n_bytes(n)
|
||||
buf = read(n)
|
||||
raise EOFError if buf == nil
|
||||
return buf if buf.size == n
|
||||
|
||||
n -= buf.size
|
||||
|
||||
while n > 0
|
||||
str = read(n)
|
||||
raise EOFError if str == nil
|
||||
buf << str
|
||||
n -= str.size
|
||||
end
|
||||
return buf
|
||||
end
|
||||
end
|
||||
|
||||
# Namespace for Metasploit branch.
|
||||
module Msf
|
||||
module Db
|
||||
|
||||
module PostgresPR
|
||||
|
||||
class ParseError < RuntimeError; end
|
||||
class DumpError < RuntimeError; end
|
||||
|
||||
|
||||
# Base class representing a PostgreSQL protocol message
|
||||
class Message
|
||||
# One character message-typecode to class map
|
||||
MsgTypeMap = Hash.new { UnknownMessageType }
|
||||
|
||||
def self.register_message_type(type)
|
||||
raise "duplicate message type registration" if MsgTypeMap.has_key?(type)
|
||||
|
||||
MsgTypeMap[type] = self
|
||||
|
||||
self.const_set(:MsgType, type)
|
||||
class_eval "def message_type; MsgType end"
|
||||
end
|
||||
|
||||
def self.read(stream, startup=false)
|
||||
type = stream.read_exactly_n_bytes(1) unless startup
|
||||
length = stream.read_exactly_n_bytes(4).unpack('N').first # FIXME: length should be signed, not unsigned
|
||||
|
||||
raise ParseError unless length >= 4
|
||||
|
||||
# initialize buffer
|
||||
buffer = Buffer.of_size(startup ? length : 1+length)
|
||||
buffer.write(type) unless startup
|
||||
buffer.write_int32_network(length)
|
||||
buffer.copy_from_stream(stream, length-4)
|
||||
|
||||
(startup ? StartupMessage : MsgTypeMap[type]).create(buffer)
|
||||
end
|
||||
|
||||
def self.create(buffer)
|
||||
obj = allocate
|
||||
obj.parse(buffer)
|
||||
obj
|
||||
end
|
||||
|
||||
def self.dump(*args)
|
||||
new(*args).dump
|
||||
end
|
||||
|
||||
def dump(body_size=0)
|
||||
buffer = Buffer.of_size(5 + body_size)
|
||||
buffer.write(self.message_type)
|
||||
buffer.write_int32_network(4 + body_size)
|
||||
yield buffer if block_given?
|
||||
raise DumpError unless buffer.at_end?
|
||||
return buffer.content
|
||||
end
|
||||
|
||||
def parse(buffer)
|
||||
buffer.position = 5
|
||||
yield buffer if block_given?
|
||||
raise ParseError, buffer.inspect unless buffer.at_end?
|
||||
end
|
||||
|
||||
def self.fields(*attribs)
|
||||
names = attribs.map {|name, type| name.to_s}
|
||||
arg_list = names.join(", ")
|
||||
ivar_list = names.map {|name| "@" + name }.join(", ")
|
||||
sym_list = names.map {|name| ":" + name }.join(", ")
|
||||
class_eval %[
|
||||
attr_accessor #{ sym_list }
|
||||
def initialize(#{ arg_list })
|
||||
#{ ivar_list } = #{ arg_list }
|
||||
end
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
class UnknownMessageType < Message
|
||||
def dump
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
class Authentification < Message
|
||||
register_message_type 'R'
|
||||
|
||||
AuthTypeMap = Hash.new { UnknownAuthType }
|
||||
|
||||
def self.create(buffer)
|
||||
buffer.position = 5
|
||||
authtype = buffer.read_int32_network
|
||||
klass = AuthTypeMap[authtype]
|
||||
obj = klass.allocate
|
||||
obj.parse(buffer)
|
||||
obj
|
||||
end
|
||||
|
||||
def self.register_auth_type(type)
|
||||
raise "duplicate auth type registration" if AuthTypeMap.has_key?(type)
|
||||
AuthTypeMap[type] = self
|
||||
self.const_set(:AuthType, type)
|
||||
class_eval "def auth_type() AuthType end"
|
||||
end
|
||||
|
||||
# the dump method of class Message
|
||||
alias message__dump dump
|
||||
|
||||
def dump
|
||||
super(4) do |buffer|
|
||||
buffer.write_int32_network(self.auth_type)
|
||||
end
|
||||
end
|
||||
|
||||
def parse(buffer)
|
||||
super do
|
||||
auth_t = buffer.read_int32_network
|
||||
raise ParseError unless auth_t == self.auth_type
|
||||
yield if block_given?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class UnknownAuthType < Authentification
|
||||
end
|
||||
|
||||
class AuthentificationOk < Authentification
|
||||
register_auth_type 0
|
||||
end
|
||||
|
||||
class AuthentificationKerberosV4 < Authentification
|
||||
register_auth_type 1
|
||||
end
|
||||
|
||||
class AuthentificationKerberosV5 < Authentification
|
||||
register_auth_type 2
|
||||
end
|
||||
|
||||
class AuthentificationClearTextPassword < Authentification
|
||||
register_auth_type 3
|
||||
end
|
||||
|
||||
module SaltedAuthentificationMixin
|
||||
attr_accessor :salt
|
||||
|
||||
def initialize(salt)
|
||||
@salt = salt
|
||||
end
|
||||
|
||||
def dump
|
||||
raise DumpError unless @salt.size == self.salt_size
|
||||
|
||||
message__dump(4 + self.salt_size) do |buffer|
|
||||
buffer.write_int32_network(self.auth_type)
|
||||
buffer.write(@salt)
|
||||
end
|
||||
end
|
||||
|
||||
def parse(buffer)
|
||||
super do
|
||||
@salt = buffer.read(self.salt_size)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class AuthentificationCryptPassword < Authentification
|
||||
register_auth_type 4
|
||||
include SaltedAuthentificationMixin
|
||||
def salt_size; 2 end
|
||||
end
|
||||
|
||||
|
||||
class AuthentificationMD5Password < Authentification
|
||||
register_auth_type 5
|
||||
include SaltedAuthentificationMixin
|
||||
def salt_size; 4 end
|
||||
end
|
||||
|
||||
class AuthentificationSCMCredential < Authentification
|
||||
register_auth_type 6
|
||||
end
|
||||
|
||||
class PasswordMessage < Message
|
||||
register_message_type 'p'
|
||||
fields :password
|
||||
|
||||
def dump
|
||||
super(@password.size + 1) do |buffer|
|
||||
buffer.write_cstring(@password)
|
||||
end
|
||||
end
|
||||
|
||||
def parse(buffer)
|
||||
super do
|
||||
@password = buffer.read_cstring
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ParameterStatus < Message
|
||||
register_message_type 'S'
|
||||
fields :key, :value
|
||||
|
||||
def dump
|
||||
super(@key.size + 1 + @value.size + 1) do |buffer|
|
||||
buffer.write_cstring(@key)
|
||||
buffer.write_cstring(@value)
|
||||
end
|
||||
end
|
||||
|
||||
def parse(buffer)
|
||||
super do
|
||||
@key = buffer.read_cstring
|
||||
@value = buffer.read_cstring
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class BackendKeyData < Message
|
||||
register_message_type 'K'
|
||||
fields :process_id, :secret_key
|
||||
|
||||
def dump
|
||||
super(4 + 4) do |buffer|
|
||||
buffer.write_int32_network(@process_id)
|
||||
buffer.write_int32_network(@secret_key)
|
||||
end
|
||||
end
|
||||
|
||||
def parse(buffer)
|
||||
super do
|
||||
@process_id = buffer.read_int32_network
|
||||
@secret_key = buffer.read_int32_network
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ReadyForQuery < Message
|
||||
register_message_type 'Z'
|
||||
fields :backend_transaction_status_indicator
|
||||
|
||||
def dump
|
||||
super(1) do |buffer|
|
||||
buffer.write_byte(@backend_transaction_status_indicator)
|
||||
end
|
||||
end
|
||||
|
||||
def parse(buffer)
|
||||
super do
|
||||
@backend_transaction_status_indicator = buffer.read_byte
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class DataRow < Message
|
||||
register_message_type 'D'
|
||||
fields :columns
|
||||
|
||||
def dump
|
||||
sz = @columns.inject(2) {|sum, col| sum + 4 + (col ? col.size : 0)}
|
||||
super(sz) do |buffer|
|
||||
buffer.write_int16_network(@columns.size)
|
||||
@columns.each {|col|
|
||||
buffer.write_int32_network(col ? col.size : -1)
|
||||
buffer.write(col) if col
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def parse(buffer)
|
||||
super do
|
||||
n_cols = buffer.read_int16_network
|
||||
@columns = (1..n_cols).collect {
|
||||
len = buffer.read_int32_network
|
||||
if len == -1
|
||||
nil
|
||||
else
|
||||
buffer.read(len)
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class CommandComplete < Message
|
||||
register_message_type 'C'
|
||||
fields :cmd_tag
|
||||
|
||||
def dump
|
||||
super(@cmd_tag.size + 1) do |buffer|
|
||||
buffer.write_cstring(@cmd_tag)
|
||||
end
|
||||
end
|
||||
|
||||
def parse(buffer)
|
||||
super do
|
||||
@cmd_tag = buffer.read_cstring
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class EmptyQueryResponse < Message
|
||||
register_message_type 'I'
|
||||
end
|
||||
|
||||
module NoticeErrorMixin
|
||||
attr_accessor :field_type, :field_values
|
||||
|
||||
def initialize(field_type=0, field_values=[])
|
||||
raise ArgumentError if field_type == 0 and not field_values.empty?
|
||||
@field_type, @field_values = field_type, field_values
|
||||
end
|
||||
|
||||
def dump
|
||||
raise ArgumentError if @field_type == 0 and not @field_values.empty?
|
||||
|
||||
sz = 1
|
||||
sz += @field_values.inject(1) {|sum, fld| sum + fld.size + 1} unless @field_type == 0
|
||||
|
||||
super(sz) do |buffer|
|
||||
buffer.write_byte(@field_type)
|
||||
break if @field_type == 0
|
||||
@field_values.each {|fld| buffer.write_cstring(fld) }
|
||||
buffer.write_byte(0)
|
||||
end
|
||||
end
|
||||
|
||||
def parse(buffer)
|
||||
super do
|
||||
@field_type = buffer.read_byte
|
||||
break if @field_type == 0
|
||||
@field_values = []
|
||||
while buffer.position < buffer.size-1
|
||||
@field_values << buffer.read_cstring
|
||||
end
|
||||
terminator = buffer.read_byte
|
||||
raise ParseError unless terminator == 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class NoticeResponse < Message
|
||||
register_message_type 'N'
|
||||
include NoticeErrorMixin
|
||||
end
|
||||
|
||||
class ErrorResponse < Message
|
||||
register_message_type 'E'
|
||||
include NoticeErrorMixin
|
||||
end
|
||||
|
||||
# TODO
|
||||
class CopyInResponse < Message
|
||||
register_message_type 'G'
|
||||
end
|
||||
|
||||
# TODO
|
||||
class CopyOutResponse < Message
|
||||
register_message_type 'H'
|
||||
end
|
||||
|
||||
class Parse < Message
|
||||
register_message_type 'P'
|
||||
fields :query, :stmt_name, :parameter_oids
|
||||
|
||||
def initialize(query, stmt_name="", parameter_oids=[])
|
||||
@query, @stmt_name, @parameter_oids = query, stmt_name, parameter_oids
|
||||
end
|
||||
|
||||
def dump
|
||||
sz = @stmt_name.size + 1 + @query.size + 1 + 2 + (4 * @parameter_oids.size)
|
||||
super(sz) do |buffer|
|
||||
buffer.write_cstring(@stmt_name)
|
||||
buffer.write_cstring(@query)
|
||||
buffer.write_int16_network(@parameter_oids.size)
|
||||
@parameter_oids.each {|oid| buffer.write_int32_network(oid) }
|
||||
end
|
||||
end
|
||||
|
||||
def parse(buffer)
|
||||
super do
|
||||
@stmt_name = buffer.read_cstring
|
||||
@query = buffer.read_cstring
|
||||
n_oids = buffer.read_int16_network
|
||||
@parameter_oids = (1..n_oids).collect {
|
||||
# TODO: zero means unspecified. map to nil?
|
||||
buffer.read_int32_network
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ParseComplete < Message
|
||||
register_message_type '1'
|
||||
end
|
||||
|
||||
class Query < Message
|
||||
register_message_type 'Q'
|
||||
fields :query
|
||||
|
||||
def dump
|
||||
super(@query.size + 1) do |buffer|
|
||||
buffer.write_cstring(@query)
|
||||
end
|
||||
end
|
||||
|
||||
def parse(buffer)
|
||||
super do
|
||||
@query = buffer.read_cstring
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class RowDescription < Message
|
||||
register_message_type 'T'
|
||||
fields :fields
|
||||
|
||||
class FieldInfo < Struct.new(:name, :oid, :attr_nr, :type_oid, :typlen, :atttypmod, :formatcode); end
|
||||
|
||||
def dump
|
||||
sz = @fields.inject(2) {|sum, fld| sum + 18 + fld.name.size + 1 }
|
||||
super(sz) do |buffer|
|
||||
buffer.write_int16_network(@fields.size)
|
||||
@fields.each { |f|
|
||||
buffer.write_cstring(f.name)
|
||||
buffer.write_int32_network(f.oid)
|
||||
buffer.write_int16_network(f.attr_nr)
|
||||
buffer.write_int32_network(f.type_oid)
|
||||
buffer.write_int16_network(f.typlen)
|
||||
buffer.write_int32_network(f.atttypmod)
|
||||
buffer.write_int16_network(f.formatcode)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def parse(buffer)
|
||||
super do
|
||||
n_fields = buffer.read_int16_network
|
||||
@fields = (1..n_fields).collect {
|
||||
f = FieldInfo.new
|
||||
f.name = buffer.read_cstring
|
||||
f.oid = buffer.read_int32_network
|
||||
f.attr_nr = buffer.read_int16_network
|
||||
f.type_oid = buffer.read_int32_network
|
||||
f.typlen = buffer.read_int16_network
|
||||
f.atttypmod = buffer.read_int32_network
|
||||
f.formatcode = buffer.read_int16_network
|
||||
f
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class StartupMessage < Message
|
||||
fields :proto_version, :params
|
||||
|
||||
def dump
|
||||
sz = @params.inject(4 + 4) {|sum, kv| sum + kv[0].size + 1 + kv[1].size + 1} + 1
|
||||
|
||||
buffer = Buffer.of_size(sz)
|
||||
buffer.write_int32_network(sz)
|
||||
buffer.write_int32_network(@proto_version)
|
||||
@params.each_pair {|key, value|
|
||||
buffer.write_cstring(key)
|
||||
buffer.write_cstring(value)
|
||||
}
|
||||
buffer.write_byte(0)
|
||||
|
||||
raise DumpError unless buffer.at_end?
|
||||
return buffer.content
|
||||
end
|
||||
|
||||
def parse(buffer)
|
||||
buffer.position = 4
|
||||
|
||||
@proto_version = buffer.read_int32_network
|
||||
@params = {}
|
||||
|
||||
while buffer.position < buffer.size-1
|
||||
key = buffer.read_cstring
|
||||
val = buffer.read_cstring
|
||||
@params[key] = val
|
||||
end
|
||||
|
||||
nul = buffer.read_byte
|
||||
raise ParseError unless nul == 0
|
||||
raise ParseError unless buffer.at_end?
|
||||
end
|
||||
end
|
||||
|
||||
class SSLRequest < Message
|
||||
fields :ssl_request_code
|
||||
|
||||
def dump
|
||||
sz = 4 + 4
|
||||
buffer = Buffer.of_size(sz)
|
||||
buffer.write_int32_network(sz)
|
||||
buffer.write_int32_network(@ssl_request_code)
|
||||
raise DumpError unless buffer.at_end?
|
||||
return buffer.content
|
||||
end
|
||||
|
||||
def parse(buffer)
|
||||
buffer.position = 4
|
||||
@ssl_request_code = buffer.read_int32_network
|
||||
raise ParseError unless buffer.at_end?
|
||||
end
|
||||
end
|
||||
|
||||
=begin
|
||||
# TODO: duplicate message-type, split into client/server messages
|
||||
class Sync < Message
|
||||
register_message_type 'S'
|
||||
end
|
||||
=end
|
||||
|
||||
class Terminate < Message
|
||||
register_message_type 'X'
|
||||
end
|
||||
|
||||
end # module PostgresPR
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,161 @@
|
|||
# This is a compatibility layer for using the pure Ruby postgres-pr instead of
|
||||
# the C interface of postgres.
|
||||
|
||||
require 'postgres_msf'
|
||||
require 'postgres/postgres-pr/connection'
|
||||
|
||||
# Namespace for Metasploit branch.
|
||||
module Msf
|
||||
module Db
|
||||
|
||||
class PGconn
|
||||
class << self
|
||||
alias connect new
|
||||
end
|
||||
|
||||
def initialize(host, port, options, tty, database, user, auth)
|
||||
uri =
|
||||
if host.nil?
|
||||
nil
|
||||
elsif host[0] != ?/
|
||||
"tcp://#{ host }:#{ port }"
|
||||
else
|
||||
"unix:#{ host }/.s.PGSQL.#{ port }"
|
||||
end
|
||||
@host = host
|
||||
@db = database
|
||||
@user = user
|
||||
@conn = PostgresPR::Connection.new(database, user, auth, uri)
|
||||
end
|
||||
|
||||
def close
|
||||
@conn.close
|
||||
end
|
||||
|
||||
attr_reader :host, :db, :user
|
||||
|
||||
def query(sql)
|
||||
PGresult.new(@conn.query(sql))
|
||||
end
|
||||
|
||||
alias exec query
|
||||
|
||||
def transaction_status
|
||||
@conn.transaction_status
|
||||
end
|
||||
|
||||
def self.escape(str)
|
||||
str.gsub("'","''").gsub("\\", "\\\\\\\\")
|
||||
end
|
||||
|
||||
def notice_processor
|
||||
@conn.notice_processor
|
||||
end
|
||||
|
||||
def notice_processor=(np)
|
||||
@conn.notice_processor = np
|
||||
end
|
||||
|
||||
def self.quote_ident(name)
|
||||
%("#{name}")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class PGresult
|
||||
include Enumerable
|
||||
|
||||
EMPTY_QUERY = 0
|
||||
COMMAND_OK = 1
|
||||
TUPLES_OK = 2
|
||||
COPY_OUT = 3
|
||||
COPY_IN = 4
|
||||
BAD_RESPONSE = 5
|
||||
NONFATAL_ERROR = 6
|
||||
FATAL_ERROR = 7
|
||||
|
||||
def each(&block)
|
||||
@result.each(&block)
|
||||
end
|
||||
|
||||
def [](index)
|
||||
@result[index]
|
||||
end
|
||||
|
||||
def initialize(res)
|
||||
@res = res
|
||||
@fields = @res.fields.map {|f| f.name}
|
||||
@result = @res.rows
|
||||
end
|
||||
|
||||
# TODO: status, getlength, cmdstatus
|
||||
|
||||
attr_reader :result, :fields
|
||||
|
||||
def num_tuples
|
||||
@result.size
|
||||
end
|
||||
|
||||
def num_fields
|
||||
@fields.size
|
||||
end
|
||||
|
||||
def fieldname(index)
|
||||
@fields[index]
|
||||
end
|
||||
|
||||
def fieldnum(name)
|
||||
@fields.index(name)
|
||||
end
|
||||
|
||||
def type(index)
|
||||
# TODO: correct?
|
||||
@res.fields[index].type_oid
|
||||
end
|
||||
|
||||
def size(index)
|
||||
raise
|
||||
# TODO: correct?
|
||||
@res.fields[index].typlen
|
||||
end
|
||||
|
||||
def getvalue(tup_num, field_num)
|
||||
@result[tup_num][field_num]
|
||||
end
|
||||
|
||||
def status
|
||||
if num_tuples > 0
|
||||
TUPLES_OK
|
||||
else
|
||||
COMMAND_OK
|
||||
end
|
||||
end
|
||||
|
||||
def cmdstatus
|
||||
@res.cmd_tag || ''
|
||||
end
|
||||
|
||||
# free the result set
|
||||
def clear
|
||||
@res = @fields = @result = nil
|
||||
end
|
||||
|
||||
# Returns the number of rows affected by the SQL command
|
||||
def cmdtuples
|
||||
case @res.cmd_tag
|
||||
when nil
|
||||
return nil
|
||||
when /^INSERT\s+(\d+)\s+(\d+)$/, /^(DELETE|UPDATE|MOVE|FETCH)\s+(\d+)$/
|
||||
$2.to_i
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class PGError < Exception
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,46 @@
|
|||
require 'strscan'
|
||||
|
||||
module Postgres::Conversion
|
||||
|
||||
def decode_array(str, delim=',', &conv_proc)
|
||||
delim = Regexp.escape(delim)
|
||||
buf = StringScanner.new(str)
|
||||
return parse_arr(buf, delim, &conv_proc)
|
||||
ensure
|
||||
raise ConversionError, "end of string expected (#{buf.rest})" unless buf.empty?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_arr(buf, delim, &conv_proc)
|
||||
# skip whitespace
|
||||
buf.skip(/\s*/)
|
||||
|
||||
raise ConversionError, "'{' expected" unless buf.get_byte == '{'
|
||||
|
||||
elems = []
|
||||
unless buf.scan(/\}/) # array is not empty
|
||||
loop do
|
||||
# skip whitespace
|
||||
buf.skip(/\s+/)
|
||||
|
||||
elems <<
|
||||
if buf.check(/\{/)
|
||||
parse_arr(buf, delim, &conv_proc)
|
||||
else
|
||||
e = buf.scan(/("((\\.)|[^"])*"|\\.|[^\}#{ delim }])*/) || raise(ConversionError)
|
||||
if conv_proc then conv_proc.call(e) else e end
|
||||
end
|
||||
|
||||
break if buf.scan(/\}/)
|
||||
break unless buf.scan(/#{ delim }/)
|
||||
end
|
||||
end
|
||||
|
||||
# skip whitespace
|
||||
buf.skip(/\s*/)
|
||||
|
||||
elems
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,26 @@
|
|||
module Postgres::Conversion
|
||||
|
||||
#
|
||||
# Encodes a string as bytea value.
|
||||
#
|
||||
# for encoding rules see:
|
||||
# http://www.postgresql.org/docs/7.4/static/datatype-binary.html
|
||||
#
|
||||
|
||||
def encode_bytea(str)
|
||||
str.gsub(/[\000-\037\047\134\177-\377]/) {|b| "\\#{ b[0].to_s(8).rjust(3, '0') }" }
|
||||
end
|
||||
|
||||
#
|
||||
# Decodes a bytea encoded string.
|
||||
#
|
||||
# for decoding rules see:
|
||||
# http://www.postgresql.org/docs/7.4/static/datatype-binary.html
|
||||
#
|
||||
def decode_bytea(str)
|
||||
str.gsub(/\\(\\|'|[0-3][0-7][0-7])/) {|s|
|
||||
if s.size == 2 then s[1,1] else s[1,3].oct.chr end
|
||||
}
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
module Postgres
|
||||
module Conversion
|
||||
class ConversionError < Exception; end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
# Namespace for Metasploit branch.
|
||||
module Msf
|
||||
module Db
|
||||
|
||||
module PostgresPR
|
||||
Version = "0.6.3-msf"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
# "Pure Ruby PostgreSQL interface," also known as "Postgres-PR" is:
|
||||
# Copyright (c) 2005, 2008 by Michael Neumann (mneumann@ntecs.de).
|
||||
#
|
||||
# Postgres-PR is released under the same terms of license as Ruby.
|
||||
#
|
||||
# The Ruby License is:
|
||||
# http://www.ruby-lang.org/en/LICENSE.txt
|
||||
#
|
||||
|
||||
require 'postgres/postgres-pr/postgres-compat'
|
||||
require 'stringio'
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'test/unit'
|
||||
require 'postgres_msf'
|
||||
|
||||
$_POSTGRESQL_TEST_SERVERNAME = 'dbsrv' # Name or IP, default: dbsrv
|
||||
$_POSTGRESQL_TEST_SERVERPORT = 5432 # Default: 5432
|
||||
$_POSTGRESQL_TEST_DATABASE = 'mydb' # Default: mydb
|
||||
$_POSTGRESQL_TEST_USERNAME = 'scott' # Default: scott
|
||||
$_POSTGRESQL_TEST_PASSWORD = 'tiger' # Default: tiger
|
||||
|
||||
class Msf::Db::PostgresPR::UnitTest < ::Test::Unit::TestCase
|
||||
|
||||
def test_connection
|
||||
srv = "tcp://#{$_POSTGRESQL_TEST_SERVERNAME}:#{$_POSTGRESQL_TEST_SERVERPORT}"
|
||||
conn = Msf::Db::PostgresPR::Connection.new($_POSTGRESQL_TEST_DATABASE,
|
||||
$_POSTGRESQL_TEST_USERNAME,
|
||||
$_POSTGRESQL_TEST_PASSWORD,
|
||||
srv)
|
||||
assert_kind_of Msf::Db::PostgresPR::Connection, conn
|
||||
assert_nothing_raised { conn.close }
|
||||
end
|
||||
|
||||
# Note that this will drop the "test" table for the named database.
|
||||
# This is a destructive act!
|
||||
def test_query
|
||||
srv = "tcp://#{$_POSTGRESQL_TEST_SERVERNAME}:#{$_POSTGRESQL_TEST_SERVERPORT}"
|
||||
conn = Msf::Db::PostgresPR::Connection.new($_POSTGRESQL_TEST_DATABASE,
|
||||
$_POSTGRESQL_TEST_USERNAME,
|
||||
$_POSTGRESQL_TEST_PASSWORD,
|
||||
srv)
|
||||
|
||||
begin
|
||||
conn.query("drop table test")
|
||||
rescue RuntimeError # Cleanup, it may or may not be there.
|
||||
end
|
||||
|
||||
assert_nothing_raised do
|
||||
conn.query("CREATE TABLE test (i int, v varchar(5))")
|
||||
conn.query(%q{INSERT INTO test VALUES (1, 'foo')})
|
||||
conn.query(%q{INSERT INTO test VALUES (2, 'bar')})
|
||||
end
|
||||
|
||||
resp = conn.query("select * from test")
|
||||
assert_equal(2, resp.rows.size)
|
||||
assert_equal(2, resp.fields.size)
|
||||
assert_equal("SELECT", resp.cmd_tag)
|
||||
assert_nothing_raised { conn.query("drop table test") }
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
Loading…
Reference in New Issue