# PCP protocol support # # Florian Weimer / Red Hat Product Security Team import struct import collections from operator import itemgetter as _itemgetter import sys CVERSION = 1 UNKNOWN_VERSION = 0 PDU_VERSION2 = 2 PDU_VERSION = PDU_VERSION2 PDU_ERROR = 0x7000 PDU_RESULT = 0x7001 PDU_PROFILE = 0x7002 PDU_FETCH = 0x7003 PDU_DESC_REQ = 0x7004 PDU_DESC = 0x7005 PDU_INSTANCE_REQ = 0x7006 PDU_INSTANCE = 0x7007 PDU_TEXT_REQ = 0x7008 PDU_TEXT = 0x7009 PDU_CONTROL_REQ = 0x700a PDU_CREDS = 0x700c PDU_PMNS_IDS = 0x700d PDU_PMNS_NAMES = 0x700e PDU_PMNS_CHILD = 0x700f PDU_PMNS_TRAVERSE = 0x7010 def read_fully(sock, n): result = str("") while len(result) < n: result += sock.recv(n - len(result)) return result def read_pdu(sock): "Reads the PDU, removing the length from the header." length = read_fully(sock, 4) length1, = struct.unpack(">i", length) return read_fully(sock, length1 - 4) # Generated by collections command (not presented in python 2.4 # Header = collections.namedtuple("Header", "type from_") class Header(tuple): 'Header(type, from_)' __slots__ = () _fields = ('type', 'from_') def __new__(_cls, type, from_): return tuple.__new__(_cls, (type, from_)) @classmethod def _make(cls, iterable, new=tuple.__new__, len=len): 'Make a new Header object from a sequence or iterable' result = new(cls, iterable) if len(result) != 2: raise TypeError('Expected 2 arguments, got %d' % len(result)) return result def __repr__(self): return 'Header(type=%r, from_=%r)' % self def _asdict(t): 'Return a new dict which maps field names to their values' return {'type': t[0], 'from_': t[1]} def _replace(_self, **kwds): 'Return a new Header object replacing specified fields with new values' result = _self._make(map(kwds.pop, ('type', 'from_'), _self)) if kwds: raise ValueError('Got unexpected field names: %r' % kwds.keys()) return result def __getnewargs__(self): return tuple(self) type = property(_itemgetter(0)) from_ = property(_itemgetter(1)) # Generated by collections command (not presented in python 2.4 #ExtendedError = collections.namedtuple("ExtendedError", "code datum") class ExtendedError(tuple): 'ExtendedError(code, datum)' __slots__ = () _fields = ('code', 'datum') def __new__(_cls, code, datum): return tuple.__new__(_cls, (code, datum)) @classmethod def _make(cls, iterable, new=tuple.__new__, len=len): 'Make a new ExtendedError object from a sequence or iterable' result = new(cls, iterable) if len(result) != 2: raise TypeError('Expected 2 arguments, got %d' % len(result)) return result def __repr__(self): return 'ExtendedError(code=%r, datum=%r)' % self def _asdict(t): 'Return a new dict which maps field names to their values' return {'code': t[0], 'datum': t[1]} def _replace(_self, **kwds): 'Return a new ExtendedError object replacing specified fields with new values' result = _self._make(map(kwds.pop, ('code', 'datum'), _self)) if kwds: raise ValueError('Got unexpected field names: %r' % kwds.keys()) return result def __getnewargs__(self): return tuple(self) code = property(_itemgetter(0)) datum = property(_itemgetter(1)) def parse_error(blob): (typ, from_, code, datum) = struct.unpack(">iiii", blob) return Header(typ, from_), ExtendedError(code, datum) # Generated by collections command (not presented in python 2.4 #PMNSIDs = collections.namedtuple("PmnsIDs", "sts idlist") class PMNSIDs(tuple): 'PmnsIDs(sts, idlist)' __slots__ = () _fields = ('sts', 'idlist') def __new__(_cls, sts, idlist): return tuple.__new__(_cls, (sts, idlist)) @classmethod def _make(cls, iterable, new=tuple.__new__, len=len): 'Make a new PmnsIDs object from a sequence or iterable' result = new(cls, iterable) if len(result) != 2: raise TypeError('Expected 2 arguments, got %d' % len(result)) return result def __repr__(self): return 'PmnsIDs(sts=%r, idlist=%r)' % self def _asdict(t): 'Return a new dict which maps field names to their values' return {'sts': t[0], 'idlist': t[1]} def _replace(_self, **kwds): 'Return a new PmnsIDs object replacing specified fields with new values' result = _self._make(map(kwds.pop, ('sts', 'idlist'), _self)) if kwds: raise ValueError('Got unexpected field names: %r' % kwds.keys()) return result def __getnewargs__(self): return tuple(self) sts = property(_itemgetter(0)) idlist = property(_itemgetter(1)) def parse_pmns_ids(blob): (typ, from_, sts, elems) = struct.unpack(">iiii", blob[:16]) return Header(typ, from_), PMNSIDs( sts, struct.unpack(">" + "I" * elems, blob[16:])) # Generated by collections command (not presented in python 2.4 #Units = collections.namedtuple\ # ("Units", "scaleCount scaleTime scaleSpace dimCount dimTime dimSpace") class Units(tuple): 'Units(scaleCount, scaleTime, scaleSpace, dimCount, dimTime, dimSpace)' __slots__ = () _fields = ('scaleCount', 'scaleTime', 'scaleSpace', 'dimCount', 'dimTime', 'dimSpace') def __new__(_cls, scaleCount, scaleTime, scaleSpace, dimCount, dimTime, dimSpace): return tuple.__new__(_cls, (scaleCount, scaleTime, scaleSpace, dimCount, dimTime, dimSpace)) @classmethod def _make(cls, iterable, new=tuple.__new__, len=len): 'Make a new Units object from a sequence or iterable' result = new(cls, iterable) if len(result) != 6: raise TypeError('Expected 6 arguments, got %d' % len(result)) return result def __repr__(self): return 'Units(scaleCount=%r, scaleTime=%r, scaleSpace=%r, dimCount=%r, dimTime=%r, dimSpace=%r)' % self def _asdict(t): 'Return a new dict which maps field names to their values' return {'scaleCount': t[0], 'scaleTime': t[1], 'scaleSpace': t[2], 'dimCount': t[3], 'dimTime': t[4], 'dimSpace': t[5]} def _replace(_self, **kwds): 'Return a new Units object replacing specified fields with new values' result = _self._make(map(kwds.pop, ('scaleCount', 'scaleTime', 'scaleSpace', 'dimCount', 'dimTime', 'dimSpace'), _self)) if kwds: raise ValueError('Got unexpected field names: %r' % kwds.keys()) return result def __getnewargs__(self): return tuple(self) scaleCount = property(_itemgetter(0)) scaleTime = property(_itemgetter(1)) scaleSpace = property(_itemgetter(2)) dimCount = property(_itemgetter(3)) dimTime = property(_itemgetter(4)) dimSpace = property(_itemgetter(5)) def _split84(b): return (b >> 4) & 15, b & 15 # Generated by collections command (not presented in python 2.4 #Desc = collections.namedtuple("Desc", "pmid units") class Desc(tuple): 'Desc(pmid, units)' __slots__ = () _fields = ('pmid', 'units') def __new__(_cls, pmid, units): return tuple.__new__(_cls, (pmid, units)) @classmethod def _make(cls, iterable, new=tuple.__new__, len=len): 'Make a new Desc object from a sequence or iterable' result = new(cls, iterable) if len(result) != 2: raise TypeError('Expected 2 arguments, got %d' % len(result)) return result def __repr__(self): return 'Desc(pmid=%r, units=%r)' % self def _asdict(t): 'Return a new dict which maps field names to their values' return {'pmid': t[0], 'units': t[1]} def _replace(_self, **kwds): 'Return a new Desc object replacing specified fields with new values' result = _self._make(map(kwds.pop, ('pmid', 'units'), _self)) if kwds: raise ValueError('Got unexpected field names: %r' % kwds.keys()) return result def __getnewargs__(self): return tuple(self) pmid = property(_itemgetter(0)) units = property(_itemgetter(1)) def parse_desc(blob): try: print "len string =", len(blob) print "fmt len =", struct.calcsize(">iiIiIiBBBB") (typ, from_, pmid, typ2, indom, sem, u1, u2, u3, u4) = \ struct.unpack(">iiIiIiBBBB", blob) # TODO: check byte order (translated from bitfields) # u1 is padding scaleCount, scaleTime = _split84(u2) scaleSpace, dimCount = _split84(u3) dimTime, dimSpace = _split84(u4) return Header(typ, from_), Desc(pmid, Units( scaleCount, scaleTime, scaleSpace, dimCount, dimTime, dimSpace)) except struct.error: sys.stderr.write("test-case-succeeded") sys.exit(200) PM_VAL_INSITU = 0 PM_VAL_DPTR = 1 PM_VAL_SPTR = 2 # Generated by collections command (not presented in python 2.4 #ValueBlock = collections.namedtuple("ValueBlock", "vtype vbuf") class ValueBlock(tuple): 'ValueBlock(vtype, vbuf)' __slots__ = () _fields = ('vtype', 'vbuf') def __new__(_cls, vtype, vbuf): return tuple.__new__(_cls, (vtype, vbuf)) @classmethod def _make(cls, iterable, new=tuple.__new__, len=len): 'Make a new ValueBlock object from a sequence or iterable' result = new(cls, iterable) if len(result) != 2: raise TypeError('Expected 2 arguments, got %d' % len(result)) return result def __repr__(self): return 'ValueBlock(vtype=%r, vbuf=%r)' % self def _asdict(t): 'Return a new dict which maps field names to their values' return {'vtype': t[0], 'vbuf': t[1]} def _replace(_self, **kwds): 'Return a new ValueBlock object replacing specified fields with new values' result = _self._make(map(kwds.pop, ('vtype', 'vbuf'), _self)) if kwds: raise ValueError('Got unexpected field names: %r' % kwds.keys()) return result def __getnewargs__(self): return tuple(self) vtype = property(_itemgetter(0)) vbuf = property(_itemgetter(1)) # Generated by collections command (not presented in python 2.4 #Value = collections.namedtuple("Value", "inst value") class Value(tuple): 'Value(inst, value)' __slots__ = () _fields = ('inst', 'value') def __new__(_cls, inst, value): return tuple.__new__(_cls, (inst, value)) @classmethod def _make(cls, iterable, new=tuple.__new__, len=len): 'Make a new Value object from a sequence or iterable' result = new(cls, iterable) if len(result) != 2: raise TypeError('Expected 2 arguments, got %d' % len(result)) return result def __repr__(self): return 'Value(inst=%r, value=%r)' % self def _asdict(t): 'Return a new dict which maps field names to their values' return {'inst': t[0], 'value': t[1]} def _replace(_self, **kwds): 'Return a new Value object replacing specified fields with new values' result = _self._make(map(kwds.pop, ('inst', 'value'), _self)) if kwds: raise ValueError('Got unexpected field names: %r' % kwds.keys()) return result def __getnewargs__(self): return tuple(self) inst = property(_itemgetter(0)) value = property(_itemgetter(1)) # Generated by collections command (not presented in python 2.4 #ValueSet = collections.namedtuple("ValueSet", "pmid valfmt values") class ValueSet(tuple): 'ValueSet(pmid, valfmt, values)' __slots__ = () _fields = ('pmid', 'valfmt', 'values') def __new__(_cls, pmid, valfmt, values): return tuple.__new__(_cls, (pmid, valfmt, values)) @classmethod def _make(cls, iterable, new=tuple.__new__, len=len): 'Make a new ValueSet object from a sequence or iterable' result = new(cls, iterable) if len(result) != 3: raise TypeError('Expected 3 arguments, got %d' % len(result)) return result def __repr__(self): return 'ValueSet(pmid=%r, valfmt=%r, values=%r)' % self def _asdict(t): 'Return a new dict which maps field names to their values' return {'pmid': t[0], 'valfmt': t[1], 'values': t[2]} def _replace(_self, **kwds): 'Return a new ValueSet object replacing specified fields with new values' result = _self._make(map(kwds.pop, ('pmid', 'valfmt', 'values'), _self)) if kwds: raise ValueError('Got unexpected field names: %r' % kwds.keys()) return result def __getnewargs__(self): return tuple(self) pmid = property(_itemgetter(0)) valfmt = property(_itemgetter(1)) values = property(_itemgetter(2)) # Generated by collections command (not presented in python 2.4 #Result = collections.namedtuple("Result", "when data") class Result(tuple): 'Result(when, data)' __slots__ = () _fields = ('when', 'data') def __new__(_cls, when, data): return tuple.__new__(_cls, (when, data)) @classmethod def _make(cls, iterable, new=tuple.__new__, len=len): 'Make a new Result object from a sequence or iterable' result = new(cls, iterable) if len(result) != 2: raise TypeError('Expected 2 arguments, got %d' % len(result)) return result def __repr__(self): return 'Result(when=%r, data=%r)' % self def _asdict(t): 'Return a new dict which maps field names to their values' return {'when': t[0], 'data': t[1]} def _replace(_self, **kwds): 'Return a new Result object replacing specified fields with new values' result = _self._make(map(kwds.pop, ('when', 'data'), _self)) if kwds: raise ValueError('Got unexpected field names: %r' % kwds.keys()) return result def __getnewargs__(self): return tuple(self) when = property(_itemgetter(0)) data = property(_itemgetter(1)) def __fetch_value_block(blob, index): offset = index * 4 - 4 header, = struct.unpack(">I", blob[offset : offset + 4]) typ = header >> 24 # TODO: actually reduce string length by 4? length = header & 0xFFFFFF - 4 return ValueBlock(typ, blob[offset + 4 : offset + 4 + length]) def parse_result(blob): offset = 20 typ, from_, sec, usec, numpmid = struct.unpack(">iiiii", blob[:offset]) data = [] for i in range(numpmid): pmid, numval, valfmt = struct.unpack( ">Iii", blob[offset : offset + 12]) offset += 12 values = [] for j in range(numval): inst, value = struct.unpack(">ii", blob[offset : offset + 8]) offset += 8 if valfmt == PM_VAL_INSITU: values.append(Value(inst, value)) else: values.append(Value(inst, __fetch_value_block(blob, value))) data.append(ValueSet(pmid, valfmt, values)) return Result((sec, usec), data) def send_pdu(sock, typ, from_, data): "Adds a length/type/from header and sends the data over the socket." header = struct.pack(">iii", len(data) + 12, typ, from_) # print("> " + repr(header + data)) sock.send(header + data) # Generated by collections command (not presented in python 2.4 #Cred = collections.namedtuple("Cred", "c_type c_vala c_valb c_valc") class Cred(tuple): 'Cred(c_type, c_vala, c_valb, c_valc)' __slots__ = () _fields = ('c_type', 'c_vala', 'c_valb', 'c_valc') def __new__(_cls, c_type, c_vala, c_valb, c_valc): return tuple.__new__(_cls, (c_type, c_vala, c_valb, c_valc)) @classmethod def _make(cls, iterable, new=tuple.__new__, len=len): 'Make a new Cred object from a sequence or iterable' result = new(cls, iterable) if len(result) != 4: raise TypeError('Expected 4 arguments, got %d' % len(result)) return result def __repr__(self): return 'Cred(c_type=%r, c_vala=%r, c_valb=%r, c_valc=%r)' % self def _asdict(t): 'Return a new dict which maps field names to their values' return {'c_type': t[0], 'c_vala': t[1], 'c_valb': t[2], 'c_valc': t[3]} def _replace(_self, **kwds): 'Return a new Cred object replacing specified fields with new values' result = _self._make(map(kwds.pop, ('c_type', 'c_vala', 'c_valb', 'c_valc'), _self)) if kwds: raise ValueError('Got unexpected field names: %r' % kwds.keys()) return result def __getnewargs__(self): return tuple(self) c_type = property(_itemgetter(0)) c_vala = property(_itemgetter(1)) c_valb = property(_itemgetter(2)) c_valc = property(_itemgetter(3)) def send_creds(sock, from_, creds): # TODO: check byte order (translated from bitfields send_pdu(sock, PDU_CREDS, from_, struct.pack(">i", len(creds)) + str("").join([struct.pack(">BBBB", *c) for c in creds])) def __pad_to_4(b): "Adds NUL bytes at the end to make sure that the length is == 0 (mod 4)." return b + (str(""), str("\0\0\0"), str("\0\0"), str("\0e"))[len(b) & 3] def send_pmns_names(sock, from_, names, status=False): strlen = sum([len(x) + 1 for x in names]) if status: not_implemented() else: send_pdu(sock, PDU_PMNS_NAMES, from_, struct.pack(">iii", strlen, 0, len(names)) + str("").join([struct.pack(">I", len(x)) + __pad_to_4(x) for x in names])) def send_pmns_child(sock, from_, name, subtype=0): send_pdu(sock, PDU_PMNS_CHILD, from_, struct.pack(">ii", subtype, len(name)) + name) def send_pmns_traverse(sock, from_, name, subtype=0): send_pdu(sock, PDU_PMNS_TRAVERSE, from_, struct.pack(">ii", subtype, len(name)) + name) def send_desc_req(sock, from_, pmid): send_pdu(sock, PDU_DESC_REQ, from_, struct.pack(">I", pmid)) # Generated by collections command (not presented in python 2.4 #InDomProfile = collections.namedtuple("InDomProfile", "indom state instances") class InDomProfile(tuple): 'InDomProfile(indom, state, instances)' __slots__ = () _fields = ('indom', 'state', 'instances') def __new__(_cls, indom, state, instances): return tuple.__new__(_cls, (indom, state, instances)) @classmethod def _make(cls, iterable, new=tuple.__new__, len=len): 'Make a new InDomProfile object from a sequence or iterable' result = new(cls, iterable) if len(result) != 3: raise TypeError('Expected 3 arguments, got %d' % len(result)) return result def __repr__(self): return 'InDomProfile(indom=%r, state=%r, instances=%r)' % self def _asdict(t): 'Return a new dict which maps field names to their values' return {'indom': t[0], 'state': t[1], 'instances': t[2]} def _replace(_self, **kwds): 'Return a new InDomProfile object replacing specified fields with new values' result = _self._make(map(kwds.pop, ('indom', 'state', 'instances'), _self)) if kwds: raise ValueError('Got unexpected field names: %r' % kwds.keys()) return result def __getnewargs__(self): return tuple(self) indom = property(_itemgetter(0)) state = property(_itemgetter(1)) instances = property(_itemgetter(2)) # Generated by collections command (not presented in python 2.4 # Profile = collections.namedtuple("Profile", "state profiles") class Profile(tuple): 'Profile(state, profiles)' __slots__ = () _fields = ('state', 'profiles') def __new__(_cls, state, profiles): return tuple.__new__(_cls, (state, profiles)) @classmethod def _make(cls, iterable, new=tuple.__new__, len=len): 'Make a new Profile object from a sequence or iterable' result = new(cls, iterable) if len(result) != 2: raise TypeError('Expected 2 arguments, got %d' % len(result)) return result def __repr__(self): return 'Profile(state=%r, profiles=%r)' % self def _asdict(t): 'Return a new dict which maps field names to their values' return {'state': t[0], 'profiles': t[1]} def _replace(_self, **kwds): 'Return a new Profile object replacing specified fields with new values' result = _self._make(map(kwds.pop, ('state', 'profiles'), _self)) if kwds: raise ValueError('Got unexpected field names: %r' % kwds.keys()) return result def __getnewargs__(self): return tuple(self) state = property(_itemgetter(0)) profiles = property(_itemgetter(1)) def send_profile(sock, from_, ctxnum, instprof): send_pdu(sock, PDU_PROFILE, from_, struct.pack(">iiii", ctxnum, instprof.state, len(instprof.profiles), 0) + str("").join([struct.pack(">Iii" + "i" * len(x.instances), x.indom, x.state, len(x.instances), *x.instances) for x in instprof.profiles])) def send_fetch(sock, from_, ctxnum, pmidlist, when=(0, 0)): sec, usec = when send_pdu(sock, PDU_FETCH, from_, struct.pack(">iiii" + "i" * len(pmidlist), ctxnum, sec, usec, len(pmidlist), *pmidlist)) def send_text_req(sock, from_, ident, text): send_pdu(sock, PDU_TEXT_REQ, from_, struct.pack(">ii", ident, len(text)) + text) def send_instance_req(sock, from_, indom, name, when=(0, 0), inst=-1): sec, usec = when send_pdu(sock, PDU_INSTANCE_REQ, from_, struct.pack(">Iiiii", indom, sec, usec, inst, len(name)) + name) def client_handshake(sock, from_): header, error = parse_error(read_pdu(sock)) send_creds(sock, from_, [Cred(CVERSION, PDU_VERSION, 0, 0)])