a65af9c8b3
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-b9f4589650da
553 lines
12 KiB
Ruby
553 lines
12 KiB
Ruby
#
|
|
# 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
|