Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/postgres/postgres-pr/message.rb
Views: 11780
# -*- coding: binary -*-1#2# Author:: Michael Neumann3# Copyright:: (c) 2005 by Michael Neumann4# License:: Same as Ruby's or BSD5#67require 'postgres_msf'8require 'postgres/buffer'9require 'rex/io/stream'1011# Monkeypatch to preserve original code intent12# (postgres-pr originally defined read_exactly_n_bytes on IO13# as a while loop)14module Rex::IO::Stream15def read_exactly_n_bytes(n)16timed_read(n)17end18end1920# Namespace for Metasploit branch.21module Msf22module Db2324module PostgresPR2526class ParseError < RuntimeError; end27class DumpError < RuntimeError; end282930# Base class representing a PostgreSQL protocol message31class Message32# One character message-typecode to class map33MsgTypeMap = Hash.new { UnknownMessageType }3435def self.register_message_type(type)36raise "duplicate message type registration" if MsgTypeMap.has_key?(type)3738MsgTypeMap[type] = self3940self.const_set(:MsgType, type)41class_eval "def message_type; MsgType end"42end4344def self.read(stream, startup=false)45type = stream.read_exactly_n_bytes(1) unless startup46length = stream.read_exactly_n_bytes(4).to_s.unpack('N').first # FIXME: length should be signed, not unsigned4748if length.nil?49raise EOFError50elsif length < 451raise ParseError52end5354# If we didn't read any bytes and startup was not set, then type will be nil, so don't continue.55unless startup56if type.nil?57return ParseError58end59end6061# initialize buffer62buffer = Buffer.of_size(startup ? length : 1+length)63buffer.write(type) unless startup64buffer.write_int32_network(length)65buffer.copy_from_stream(stream, length-4)6667(startup ? StartupMessage : MsgTypeMap[type]).create(buffer)68end6970def self.create(buffer)71obj = allocate72obj.parse(buffer)73obj74end7576def self.dump(*args)77new(*args).dump78end7980def dump(body_size=0)81buffer = Buffer.of_size(5 + body_size)82buffer.write(self.message_type)83buffer.write_int32_network(4 + body_size)84yield buffer if block_given?85raise DumpError unless buffer.at_end?86return buffer.content87end8889def parse(buffer)90buffer.position = 591yield buffer if block_given?92raise ParseError, buffer.inspect unless buffer.at_end?93end9495def self.fields(*attribs)96names = attribs.map {|name, type| name.to_s}97arg_list = names.join(", ")98ivar_list = names.map {|name| "@" + name }.join(", ")99sym_list = names.map {|name| ":" + name }.join(", ")100class_eval %[101attr_accessor #{ sym_list }102def initialize(#{ arg_list })103#{ ivar_list } = #{ arg_list }104end105]106end107end108109class UnknownMessageType < Message110def dump111raise112end113end114115class Authentication < Message116register_message_type 'R'117118AuthTypeMap = {}119120def self.create(buffer)121buffer.position = 5122authtype = buffer.read_int32_network123unless AuthTypeMap.key? authtype124return UnknownAuthType.new(authtype, buffer)125end126127klass = AuthTypeMap[authtype]128obj = klass.allocate129obj.parse(buffer)130obj131end132133def self.register_auth_type(type)134raise "duplicate auth type registration" if AuthTypeMap.has_key?(type)135AuthTypeMap[type] = self136self.const_set(:AuthType, type)137class_eval "def auth_type() AuthType end"138end139140# the dump method of class Message141alias message__dump dump142143def dump144super(4) do |buffer|145buffer.write_int32_network(self.auth_type)146end147end148149def parse(buffer)150super do151auth_t = buffer.read_int32_network152raise ParseError unless auth_t == self.auth_type153yield if block_given?154end155end156end157158class UnknownAuthType < Authentication159attr_reader :auth_type160attr_reader :buffer161162def initialize(auth_type, buffer)163@auth_type = auth_type164@buffer = buffer165end166end167168class AuthentificationOk < Authentication169register_auth_type 0170end171172class AuthentificationKerberosV4 < Authentication173register_auth_type 1174end175176class AuthentificationKerberosV5 < Authentication177register_auth_type 2178end179180class AuthentificationClearTextPassword < Authentication181register_auth_type 3182end183184module SaltedAuthentificationMixin185attr_accessor :salt186187def initialize(salt)188@salt = salt189end190191def dump192raise DumpError unless @salt.size == self.salt_size193194message__dump(4 + self.salt_size) do |buffer|195buffer.write_int32_network(self.auth_type)196buffer.write(@salt)197end198end199200def parse(buffer)201super do202@salt = buffer.read(self.salt_size)203end204end205end206207class AuthentificationCryptPassword < Authentication208register_auth_type 4209include SaltedAuthentificationMixin210def salt_size; 2 end211end212213214class AuthentificationMD5Password < Authentication215register_auth_type 5216include SaltedAuthentificationMixin217def salt_size; 4 end218end219220class AuthentificationSCMCredential < Authentication221register_auth_type 6222end223224# SASL Overview225# https://www.postgresql.org/docs/current/sasl-authentication.html226#227# Binary format:228# https://www.postgresql.org/docs/current/protocol-message-formats.html229class AuthenticationSASL < Authentication230# Int32(10) - Specifies that SASL authentication is required.231register_auth_type 10232233# @return [Array<String>] Name of a SASL authentication mechanisms234attr_reader :mechanisms235236# @param [Array<String>] mechanisms237def initialize(mechanisms: [])238@mechanisms = mechanisms239end240241def dump242auth_type_byte_size = 4243mechanism_bytes_size = mechanisms.sum(&:size) + (mechanisms.size + 1)244message__dump(auth_type_byte_size + mechanism_bytes_size) do |buffer|245buffer.write_int32_network(self.auth_type)246mechanisms.each do |mechanism|247buffer.write_cstring(mechanism)248end249buffer.write(Buffer::NUL)250end251end252253def parse(buffer)254super do255# The message body is a list of SASL authentication mechanisms, in the256# server's order of preference. A zero byte is required as terminator after257# the last authentication mechanism name.258# https://github.com/postgres/postgres/blob/74a2dfee2255a1bace9b0053d014c4efa2823f4d/doc/src/sgml/protocol.sgml#L3584-L3602259@mechanisms ||= []260while buffer.peek != Buffer::NUL261@mechanisms << buffer.read_cstring262end263_null = buffer.read(1)264end265end266end267268# AuthenticationSASLContinue (B)269# https://www.postgresql.org/docs/current/protocol-message-formats.html270class AuthenticationSASLContinue < Authentication271# Int32(11) - Specifies that this message contains a SASL challenge.272register_auth_type 11273274# @return [String] SASL data, specific to the SASL mechanism being used.275attr_reader :value276277# @param [String, nil] value278def initialize(value: nil)279@value = value280end281282def dump283auth_type_byte_size = 4284value_size = value.size285message__dump(auth_type_byte_size + value_size) do |buffer|286buffer.write_int32_network(self.auth_type)287buffer.write(value)288end289end290291def parse(buffer)292super do293@value = buffer.read_rest294end295end296end297298# AuthenticationSASLFinal (B)299# https://www.postgresql.org/docs/current/protocol-message-formats.html300class AuthenticationSASLFinal < Authentication301# Int32(11) - Specifies that this message contains a SASL challenge.302register_auth_type 12303304# @return [String] SASL outcome "additional data", specific to the SASL mechanism being used.305attr_reader :value306307# @param [String] value308def initialize(value:)309@value = value310end311312def dump313auth_type_byte_size = 4314value_size = value.size315message__dump(auth_type_byte_size + value_size) do |buffer|316buffer.write_int32_network(self.auth_type)317buffer.write(value)318end319end320321def parse(buffer)322super do323@value = buffer.read_rest324end325end326end327328class PasswordResponseMessage < Message329# Identifies the message as a password response. Note that this is also used for GSSAPI, SSPI and SASL response messages.330# The exact message type can be deduced from the context.331register_message_type 'p'332end333334class PasswordMessage < PasswordResponseMessage335fields :password336337def dump338super(@password.size + 1) do |buffer|339buffer.write_cstring(@password)340end341end342343def parse(buffer)344super do345@password = buffer.read_cstring346end347end348end349350# SASLInitialResponse (F). The client sends a SASLInitialResponse after choosing a SASL mechanism. The message includes the name of the selected351# mechanism, and an optional Initial Client Response, if the selected mechanism uses that.352#353# https://www.postgresql.org/docs/current/protocol-message-formats.html354# https://www.postgresql.org/docs/current/sasl-authentication.html355class SaslInitialResponseMessage < PasswordResponseMessage356357# @return [String] Name of the SASL authentication mechanism that the client selected.358attr_reader :mechanism359360# @return [String] SASL mechanism specific "Initial Response" - specific to the SASL mechanism used361attr_reader :value362363# @param [String] mechanism364# @param [String] value365def initialize(mechanism: nil, value: nil)366@mechanism = mechanism367@value = value368end369370def dump371mechanism_size = mechanism.size + Buffer::NUL.size372value_size_prefix_size = 4373value_size = value.size374message_size = mechanism_size + value_size_prefix_size + value_size375376super(message_size) do |buffer|377buffer.write_cstring(mechanism)378buffer.write_int32_network(value_size)379buffer.write(value)380end381end382383def parse(buffer)384super do385@mechanism = buffer.read_cstring386_value_size_prefix_size = buffer.read_int32_network387@value = buffer.read_rest388end389end390391def ==(other)392self.class == other.class &&393@mechanism == other.mechanism &&394@value == other.value395end396end397398# SASLResponse (F)399#400# https://www.postgresql.org/docs/current/protocol-message-formats.html401# https://www.postgresql.org/docs/current/sasl-authentication.html402class SASLResponseMessage < PasswordResponseMessage403404# @return [String] SASL mechanism specific "Initial Response" - specific to the SASL mechanism used405attr_reader :value406407# @param [String] value408def initialize(value: nil)409@value = value410end411412def dump413super(value.size) do |buffer|414buffer.write(value)415end416end417418def parse(buffer)419super do420@value = buffer.read_rest421end422end423424def ==(other)425self.class == other.class &&426@value == other.value427end428end429430class ParameterStatus < Message431register_message_type 'S'432fields :key, :value433434def dump435super(@key.size + 1 + @value.size + 1) do |buffer|436buffer.write_cstring(@key)437buffer.write_cstring(@value)438end439end440441def parse(buffer)442super do443@key = buffer.read_cstring444@value = buffer.read_cstring445end446end447end448449class BackendKeyData < Message450register_message_type 'K'451fields :process_id, :secret_key452453def dump454super(4 + 4) do |buffer|455buffer.write_int32_network(@process_id)456buffer.write_int32_network(@secret_key)457end458end459460def parse(buffer)461super do462@process_id = buffer.read_int32_network463@secret_key = buffer.read_int32_network464end465end466end467468class ReadyForQuery < Message469register_message_type 'Z'470fields :backend_transaction_status_indicator471472def dump473super(1) do |buffer|474buffer.write_byte(@backend_transaction_status_indicator)475end476end477478def parse(buffer)479super do480@backend_transaction_status_indicator = buffer.read_byte481end482end483end484485class DataRow < Message486register_message_type 'D'487fields :columns488489def dump490sz = @columns.inject(2) {|sum, col| sum + 4 + (col ? col.size : 0)}491super(sz) do |buffer|492buffer.write_int16_network(@columns.size)493@columns.each {|col|494buffer.write_int32_network(col ? col.size : -1)495buffer.write(col) if col496}497end498end499500def parse(buffer)501super do502n_cols = buffer.read_int16_network503@columns = (1..n_cols).collect {504len = buffer.read_int32_network505if len == -1506nil507else508buffer.read(len)509end510}511end512end513end514515class CommandComplete < Message516register_message_type 'C'517fields :cmd_tag518519def dump520super(@cmd_tag.size + 1) do |buffer|521buffer.write_cstring(@cmd_tag)522end523end524525def parse(buffer)526super do527@cmd_tag = buffer.read_cstring528end529end530end531532class EmptyQueryResponse < Message533register_message_type 'I'534end535536module NoticeErrorMixin537attr_accessor :field_type, :field_values538539def initialize(field_type=0, field_values=[])540raise ArgumentError if field_type == 0 and not field_values.empty?541@field_type, @field_values = field_type, field_values542end543544def dump545raise ArgumentError if @field_type == 0 and not @field_values.empty?546547sz = 1548sz += @field_values.inject(1) {|sum, fld| sum + fld.size + 1} unless @field_type == 0549550super(sz) do |buffer|551buffer.write_byte(@field_type)552break if @field_type == 0553@field_values.each {|fld| buffer.write_cstring(fld) }554buffer.write_byte(0)555end556end557558def parse(buffer)559super do560@field_type = buffer.read_byte561break if @field_type == 0562@field_values = []563while buffer.position < buffer.size-1564@field_values << buffer.read_cstring565end566terminator = buffer.read_byte567raise ParseError unless terminator == 0568end569end570end571572class NoticeResponse < Message573register_message_type 'N'574include NoticeErrorMixin575end576577class ErrorResponse < Message578register_message_type 'E'579include NoticeErrorMixin580end581582# TODO583class CopyInResponse < Message584register_message_type 'G'585end586587# TODO588class CopyOutResponse < Message589register_message_type 'H'590end591592class Parse < Message593register_message_type 'P'594fields :query, :stmt_name, :parameter_oids595596def initialize(query, stmt_name="", parameter_oids=[])597@query, @stmt_name, @parameter_oids = query, stmt_name, parameter_oids598end599600def dump601sz = @stmt_name.size + 1 + @query.size + 1 + 2 + (4 * @parameter_oids.size)602super(sz) do |buffer|603buffer.write_cstring(@stmt_name)604buffer.write_cstring(@query)605buffer.write_int16_network(@parameter_oids.size)606@parameter_oids.each {|oid| buffer.write_int32_network(oid) }607end608end609610def parse(buffer)611super do612@stmt_name = buffer.read_cstring613@query = buffer.read_cstring614n_oids = buffer.read_int16_network615@parameter_oids = (1..n_oids).collect {616# TODO: zero means unspecified. map to nil?617buffer.read_int32_network618}619end620end621end622623class ParseComplete < Message624register_message_type '1'625end626627class Query < Message628register_message_type 'Q'629fields :query630631def dump632super(@query.size + 1) do |buffer|633buffer.write_cstring(@query)634end635end636637def parse(buffer)638super do639@query = buffer.read_cstring640end641end642end643644class RowDescription < Message645register_message_type 'T'646fields :fields647648class FieldInfo < Struct.new(:name, :oid, :attr_nr, :type_oid, :typlen, :atttypmod, :formatcode); end649650def dump651sz = @fields.inject(2) {|sum, fld| sum + 18 + fld.name.size + 1 }652super(sz) do |buffer|653buffer.write_int16_network(@fields.size)654@fields.each { |f|655buffer.write_cstring(f.name)656buffer.write_int32_network(f.oid)657buffer.write_int16_network(f.attr_nr)658buffer.write_int32_network(f.type_oid)659buffer.write_int16_network(f.typlen)660buffer.write_int32_network(f.atttypmod)661buffer.write_int16_network(f.formatcode)662}663end664end665666def parse(buffer)667super do668n_fields = buffer.read_int16_network669@fields = (1..n_fields).collect {670f = FieldInfo.new671f.name = buffer.read_cstring672f.oid = buffer.read_int32_network673f.attr_nr = buffer.read_int16_network674f.type_oid = buffer.read_int32_network675f.typlen = buffer.read_int16_network676f.atttypmod = buffer.read_int32_network677f.formatcode = buffer.read_int16_network678f679}680end681end682end683684class StartupMessage < Message685fields :proto_version, :params686687def dump688sz = @params.inject(4 + 4) {|sum, kv| sum + kv[0].size + 1 + kv[1].size + 1} + 1689690buffer = Buffer.of_size(sz)691buffer.write_int32_network(sz)692buffer.write_int32_network(@proto_version)693@params.each_pair {|key, value|694buffer.write_cstring(key)695buffer.write_cstring(value)696}697buffer.write_byte(0)698699raise DumpError unless buffer.at_end?700return buffer.content701end702703def parse(buffer)704buffer.position = 4705706@proto_version = buffer.read_int32_network707@params = {}708709while buffer.position < buffer.size-1710key = buffer.read_cstring711val = buffer.read_cstring712@params[key] = val713end714715nul = buffer.read_byte716raise ParseError unless nul == 0717raise ParseError unless buffer.at_end?718end719end720721class SSLRequest < Message722fields :ssl_request_code723724def dump725sz = 4 + 4726buffer = Buffer.of_size(sz)727buffer.write_int32_network(sz)728buffer.write_int32_network(@ssl_request_code)729raise DumpError unless buffer.at_end?730return buffer.content731end732733def parse(buffer)734buffer.position = 4735@ssl_request_code = buffer.read_int32_network736raise ParseError unless buffer.at_end?737end738end739740=begin741# TODO: duplicate message-type, split into client/server messages742class Sync < Message743register_message_type 'S'744end745=end746747class Terminate < Message748register_message_type 'X'749end750751end # module PostgresPR752753end754end755756757