Files
metasploit-gs/lib/postgres/postgres-pr/message.rb
T
Tod Beardsley a65af9c8b3 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-b9f4589650da
2010-02-01 19:49:36 +00:00

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