b57613b74fa5eb04436bba51bb1aed05593098ad
[jelmer/openchange.git] / mapiproxy / services / ocsmanager / rpcproxy / rpcproxy / packets.py
1 # packets.py -- OpenChange RPC-over-HTTP implementation
2 #
3 # Copyright (C) 2012  Wolfgang Sourdeau <wsourdeau@inverse.ca>
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #   
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #   
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18
19 import logging
20 from socket import _socketobject, MSG_WAITALL
21 from struct import pack, unpack_from
22 from uuid import UUID
23
24
25 PFC_FIRST_FRAG = 1
26 PFC_LAST_FRAG = 2
27 PFC_PENDING_CANCEL = 4
28 PFC_SUPPORT_HEADER_SIGN = 4
29 PFC_RESERVED_1 = 8
30 PFC_CONC_MPX = 16
31 PFC_DID_NOT_EXECUTE = 32
32 PFC_MAYBE = 64
33 PFC_OBJECT_UUID = 128
34 PFC_FLAG_LABELS = ("PFC_FIRST_FRAG",
35                    "PFC_LAST_FRAG", 
36                    "PFC_(PENDING_CANCEL|SUPPORT_HEADER_SIGN)", 
37                    "PFC_RESERVED_1", 
38                    "PFC_CONC_MPX", 
39                    "PFC_DID_NOT_EXECUTE",
40                    "PFC_MAYBE", 
41                    "PFC_OBJECT_UUID")
42
43
44 # taken from dcerpc.idl
45 DCERPC_PKT_REQUEST = 0
46 DCERPC_PKT_PING = 1
47 DCERPC_PKT_RESPONSE = 2
48 DCERPC_PKT_FAULT = 3
49 DCERPC_PKT_WORKING = 4
50 DCERPC_PKT_NOCALL = 5
51 DCERPC_PKT_REJECT = 6
52 DCERPC_PKT_ACK = 7
53 DCERPC_PKT_CL_CANCEL = 8
54 DCERPC_PKT_FACK = 9
55 DCERPC_PKT_CANCEL_ACK = 10
56 DCERPC_PKT_BIND = 11
57 DCERPC_PKT_BIND_ACK = 12
58 DCERPC_PKT_BIND_NAK = 13
59 DCERPC_PKT_ALTER = 14
60 DCERPC_PKT_ALTER_RESP = 15
61 DCERPC_PKT_AUTH_3 = 16
62 DCERPC_PKT_SHUTDOWN = 17
63 DCERPC_PKT_CO_CANCEL = 18
64 DCERPC_PKT_ORPHANED = 19
65 DCERPC_PKT_RTS = 20
66 DCERPC_PKG_LABELS = ("DCERPC_PKT_REQUEST",
67                      "DCERPC_PKT_PING",
68                      "DCERPC_PKT_RESPONSE",
69                      "DCERPC_PKT_FAULT",
70                      "DCERPC_PKT_WORKING",
71                      "DCERPC_PKT_NOCALL",
72                      "DCERPC_PKT_REJECT",
73                      "DCERPC_PKT_ACK",
74                      "DCERPC_PKT_CL_CANCEL",
75                      "DCERPC_PKT_FACK",
76                      "DCERPC_PKT_CANCEL_ACK",
77                      "DCERPC_PKT_BIND",
78                      "DCERPC_PKT_BIND_ACK",
79                      "DCERPC_PKT_BIND_NAK",
80                      "DCERPC_PKT_ALTERA",
81                      "DCERPC_PKT_ALTER_RESP",
82                      "DCERPC_PKT_AUTH_3",
83                      "DCERPC_PKT_SHUTDOWN",
84                      "DCERPC_PKT_CO_CANCEL",
85                      "DCERPC_PKT_ORPHANED",
86                      "DCERPC_PKT_RTS")
87
88 RTS_FLAG_NONE = 0
89 RTS_FLAG_PING = 1
90 RTS_FLAG_OTHER_CMD = 2
91 RTS_FLAG_RECYCLE_CHANNEL = 4
92 RTS_FLAG_IN_CHANNEL = 8
93 RTS_FLAG_OUT_CHANNEL = 0x10
94 RTS_FLAG_EOF = 0x20
95 RTS_FLAG_ECHO = 0x40
96 RTS_FLAG_LABELS = ("RTS_FLAG_PING",
97                    "RTS_FLAG_OTHER_CMD",
98                    "RTS_FLAG_RECYCLE_CHANNEL",
99                    "RTS_FLAG_IN_CHANNEL",
100                    "RTS_FLAG_OUT_CHANNEL",
101                    "RTS_FLAG_EOF",
102                    "RTS_FLAG_ECHO")
103
104 RTS_CMD_RECEIVE_WINDOW_SIZE = 0
105 RTS_CMD_FLOW_CONTROL_ACK = 1
106 RTS_CMD_CONNECTION_TIMEOUT = 2
107 RTS_CMD_COOKIE = 3
108 RTS_CMD_CHANNEL_LIFETIME = 4
109 RTS_CMD_CLIENT_KEEPALIVE = 5
110 RTS_CMD_VERSION = 6
111 RTS_CMD_EMPTY = 7
112 RTS_CMD_PADDING = 8
113 RTS_CMD_NEGATIVE_ANCE = 9
114 RTS_CMD_ANCE = 10
115 RTS_CMD_CLIENT_ADDRESS = 11
116 RTS_CMD_ASSOCIATION_GROUP_ID = 12
117 RTS_CMD_DESTINATION = 13
118 RTS_CMD_PING_TRAFFIC_SENT_NOTIFY = 14
119
120 RTS_CMD_SIZES = (8, 28, 8, 20, 8, 8, 8, 4, 8, 4, 4, 8, 20, 8, 8)
121 RTS_CMD_DATA_LABELS = ("ReceiveWindowSize",
122                        "FlowControlAck",
123                        "ConnectionTimeout",
124                        "Cookie",
125                        "ChannelLifetime",
126                        "ClientKeepalive",
127                        "Version",
128                        "Empty",
129                        "Padding",
130                        "NegativeANCE",
131                        "ANCE",
132                        "ClientAddress",
133                        "AssociationGroupId",
134                        "Destination",
135                        "PingTrafficSentNotify")
136
137 RPC_C_AUTHN_NONE = 0x0
138 RPC_C_AUTHN_GSS_NEGOTIATE = 0x9 # SPNEGO
139 RPC_C_AUTHN_WINNT = 0xa # NTLM
140 RPC_C_AUTHN_GSS_SCHANNEL = 0xe # TLS
141 RPC_C_AUTHN_GSS_KERBEROS = 0x10 # Kerberos
142 RPC_C_AUTHN_NETLOGON = 0x44 # Netlogon
143 RPC_C_AUTHN_DEFAULT = 0xff # (NTLM)
144
145 RPC_C_AUTHN_LEVEL_DEFAULT = 0
146 RPC_C_AUTHN_LEVEL_NONE = 1
147 RPC_C_AUTHN_LEVEL_CONNECT = 2
148 RPC_C_AUTHN_LEVEL_CALL = 3
149 RPC_C_AUTHN_LEVEL_PKT = 4
150 RPC_C_AUTHN_LEVEL_PKT_INTEGRITY = 5
151 RPC_C_AUTHN_LEVEL_PKT_PRIVACY = 6
152
153
154 class RTSParsingException(IOError):
155     """This exception occurs when a serious issue occurred while parsing an
156     RTS packet.
157
158     """
159
160     pass
161
162
163 class RPCPacket(object):
164     def __init__(self, data, logger=None):
165         self.logger = logger
166
167         # BLOB level
168         self.data = data
169         self.size = 0
170
171         # parsed offset from the start of the "data" blob
172         self.offset = 0
173
174         # header is common to all PDU
175         self.header = None
176
177     @staticmethod
178     def from_file(input_file, logger=None):
179         """This static method acts as a constructor and returns an input
180         packet with the proper class, based on the packet headers.
181         The "input_file" parameter must either be a file or a sockect object.
182
183         """
184
185         if isinstance(input_file, _socketobject):
186             def read_file(count):
187                 return input_file.recv(count, MSG_WAITALL)
188         elif hasattr(file, "read") and callable(file.read):
189             def read_file(count):
190                 return input_file.read(count)
191         else:
192             raise ValueError("'input_file' must either be a socket object or"
193                              " provide a 'read' method")
194
195         fields = ("rpc_vers", "rpc_vers_minor", "ptype", "pfc_flags", "drep",
196                   "frag_length", "auth_length", "call_id")
197
198         header_data = read_file(16)
199         # TODO: value validation
200         values = unpack_from("<bbbblhhl", header_data)
201         if values[2] == DCERPC_PKT_FAULT:
202             packet_class = RPCFaultPacket
203         elif values[2] == DCERPC_PKT_BIND_ACK:
204             packet_class = RPCBindACKPacket
205         elif values[2] == DCERPC_PKT_BIND_NAK:
206             packet_class = RPCBindNAKPacket
207         elif values[2] == DCERPC_PKT_RTS:
208             packet_class = RPCRTSPacket
209         else:
210             packet_class = RPCPacket
211         body_data = read_file(values[5] - 16)
212
213         packet = packet_class(header_data + body_data, logger)
214         packet.header = dict(zip(fields, values))
215         packet.offset = 16
216         packet.size = values[5]
217         packet.parse()
218
219         return packet
220
221     def parse(self):
222         pass
223
224     def pretty_dump(self):
225         (fields, values) = self.make_dump_output()
226
227         output = ["%s: %s" % (fields[pos], str(values[pos]))
228                   for pos in xrange(len(fields))]
229
230         return "; ".join(output)
231
232     def make_dump_output(self):
233         values = []
234         
235         ptype = self.header["ptype"]
236         values.append(DCERPC_PKG_LABELS[ptype])
237
238         flags = self.header["pfc_flags"]
239         if flags == 0:
240             values.append("None")
241         else:
242             flag_values = []
243             for exp in xrange(7):
244                 flag = 1 << exp
245                 if flags & flag > 0:
246                     flag_values.append(PFC_FLAG_LABELS[exp])
247             values.append(", ".join(flag_values))
248
249         fields = ["ptype", "pfc_flags", "drep", "frag_length",
250                   "auth_length", "call_id"]
251         for field in fields[2:]:
252             values.append(self.header[field])
253
254         return (fields, values)
255
256
257
258 # fault PDU (stub)
259 class RPCFaultPacket(RPCPacket):
260     def __init__(self, data, logger=None):
261         RPCPacket.__init__(self, data, logger)
262
263
264 # bind_ack PDU (incomplete)
265 class RPCBindACKPacket(RPCPacket):
266     def __init__(self, data, logger=None):
267         RPCPacket.__init__(self, data, logger)
268         self.ntlm_payload = None
269         
270     def parse(self):
271         auth_offset = self.header["frag_length"] - self.header["auth_length"]
272         self.ntlm_payload = self.data[auth_offset:]
273
274
275 # bind_nak PDU (stub)
276 class RPCBindNAKPacket(RPCPacket):
277     def __init__(self, data, logger=None):
278         RPCPacket.__init__(self, data, logger)
279
280
281 # FIXME: command parameters are either int32 values or binary blobs, both when
282 # parsing and when producing
283 class RPCRTSPacket(RPCPacket):
284     parsers = None
285
286     def __init__(self, data, logger=None):
287         RPCPacket.__init__(self, data, logger)
288
289         # RTS commands
290         self.commands = []
291
292     def parse(self):
293         fields = ("flags", "nbr_commands")
294         values = unpack_from("<hh", self.data, self.offset)
295         self.offset = self.offset + 4
296         self.header.update(zip(fields, values))
297
298         for counter in xrange(self.header["nbr_commands"]):
299             self._parse_command()
300
301         if (self.size != self.offset):
302             raise RTSParsingException("sizes do not match: expected = %d,"
303                                       " actual = %d"
304                                       % (self.size, self.offset))
305
306     def _parse_command(self):
307         (command_type,) = unpack_from("<l", self.data, self.offset)
308         if command_type < 0 or command_type > 15:
309             raise RTSParsingException("command type unknown: %d"
310                                       % command_type)
311         self.offset = self.offset + 4
312
313         command = {"type": command_type}
314         command_size = RTS_CMD_SIZES[command_type]
315         if command_size > 4:
316             data_size = command_size - 4
317             if command_type in self.parsers:
318                 parser = self.parsers[command_type]
319                 data_value = parser(self, data_size)
320             elif data_size == 4:
321                 # commands with int32 values
322                 (data_value,) = unpack_from("<l", self.data, self.offset)
323                 self.offset = self.offset + 4
324             else:
325                 raise RTSParsingException("command is badly handled: %d"
326                                           % command_type)
327
328             data_label = RTS_CMD_DATA_LABELS[command_type]
329             command[data_label] = data_value
330
331         self.commands.append(command)
332
333     def _parse_command_flow_control_ack(self, data_size):
334         data_blob = self.data[self.offset:self.offset+data_size]
335         self.offset = self.offset + data_size
336         # dumb method
337         return data_blob
338     
339     def _parse_command_cookie(self, data_size):
340         data_blob = self.data[self.offset:self.offset+data_size]
341         self.offset = self.offset + data_size
342         # dumb method
343         return data_blob
344
345     def _parse_command_padding_data(self, data_size):
346         # the length of the padding bytes is specified in the
347         # ConformanceCount field
348         (count,) = unpack_from("<l", self.data, self.offset)
349         self.offset = self.offset + 4
350
351         data_blob = self.data[self.offset:self.offset+count]
352         self.offset = self.offset + count
353
354         return data_value
355
356     def _parse_command_client_address(self, data_blob):
357         (address_type,) = unpack_from("<l", self.data, self.offset)
358         self.offset = self.offset + 4
359
360         if address_type == 0: # ipv4
361             address_size = 4
362         elif address_type == 1: # ipv6
363             address_size = 16
364         else:
365             raise RTSParsingException("unknown client address type: %d"
366                                       % address_type)
367
368         data_blob = self.data[self.offset:self.offset+address_size]
369
370         # compute offset with padding, which is ignored
371         self.offset = self.offset + address_size + 12
372
373         return data_value
374
375     def make_dump_output(self):
376         (fields, values) = RPCPacket.make_dump_output(self)
377         fields.extend(("flags", "nbr_commands"))
378
379         flags = self.header["flags"]
380         if flags == RTS_FLAG_NONE:
381             values.append("RTS_FLAG_NONE")
382         else:
383             flags_value = []
384             for exp in xrange(7):
385                 flag = 1 << exp
386                 if flags & flag > 0:
387                     flags_value.append(RTS_FLAG_LABELS[exp])
388             values.append(", ".join(flags_value))
389
390         values.append(self.header["nbr_commands"])
391
392         return (fields, values)
393
394
395 # Those are the parser method for commands with a size > 4. They are defined
396 # here since the "RPCRTSPacket" symbol is not accessible as long as the class
397 # definition is not over
398 RPCRTSPacket.parsers = {RTS_CMD_FLOW_CONTROL_ACK: RPCRTSPacket._parse_command_flow_control_ack,
399                         RTS_CMD_COOKIE: RPCRTSPacket._parse_command_cookie,
400                         RTS_CMD_ASSOCIATION_GROUP_ID: RPCRTSPacket._parse_command_cookie,
401                         RTS_CMD_PADDING: RPCRTSPacket._parse_command_padding_data,
402                         RTS_CMD_CLIENT_ADDRESS: RPCRTSPacket._parse_command_client_address}
403
404
405
406 ### OUT packets
407
408 # bind PDU (strict minimum required for NTLMSSP auth)
409 class RPCBindOutPacket(object):
410     def __init__(self, logger=None):
411         self.logger = logger
412
413         self.size = 0
414         self.data = None
415
416         self.call_id = 1
417         self.ntlm_payload = None
418
419     def make(self):
420         if self.data is None:
421             self._make_packet_data()
422
423         return self.data
424
425     def _make_packet_data(self):
426         if self.ntlm_payload is None:
427             raise ValueError("'ntlm_payload' attribute must not be None")
428
429         ntlm_payload_size = len(self.ntlm_payload)
430         align_modulo = ntlm_payload_size % 4
431         if align_modulo > 0:
432             padding = (4 - align_modulo) * "\0"
433         else:
434             padding = ""
435         len_padding = len(padding)
436
437
438         # rfr: 1544f5e0-613c-11d1-93df-00c04fd7bd09, v1
439         # mgmt: afa8bd80-7d8a-11c9-bef4-08002b102989, v1
440         svc_guid = UUID('{afa8bd80-7d8a-11c9-bef4-08002b102989}')
441         iface_version_major = 1
442         iface_version_minor = 0
443
444         p_content_elem = ("\x01\x00\x00\x00\x00\x00\x01\x00"
445                           "%s%s"
446                           "\x04\x5d\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00"
447                           "\x2b\x10\x48\x60\x02\x00\x00\x00"
448                           % (svc_guid.bytes_le,
449                              pack("<hh",
450                                   iface_version_major,
451                                   iface_version_minor)))
452         # p_content_elem = ("\x00\x00\x00\x00")
453         len_p_content_elem = len(p_content_elem)
454
455         header_data = pack("<bbbbbbbbhhl hhl %ds bbbbl" % len_p_content_elem,
456
457                            ## common headers:
458                            5, 0, # rpc_vers, rpc_vers_minor
459                            DCERPC_PKT_BIND, # ptype
460                            # pfc_flags:
461                            PFC_FIRST_FRAG
462                            | PFC_LAST_FRAG
463                            | PFC_SUPPORT_HEADER_SIGN,
464                            # | PFC_CONC_MPX,
465                            # drep: RPC spec chap14.htm (Data Representation Format Label)
466                            (1 << 4) | 0, 0, 0, 0,
467                            (32 + ntlm_payload_size + len_padding + len_p_content_elem), # frag_length
468                            ntlm_payload_size + len_padding, # auth_length
469                            self.call_id, # call_id
470
471                            ## bind specific:
472                            4088, 4088, # max_xmit/recv_frag
473                            0, # assoc_group_id
474
475                            # p_context_elem
476                            p_content_elem,
477
478                            # p_context_elem (flattened to int32):
479                            # 0,
480
481                            # sec_trailer:
482                            RPC_C_AUTHN_WINNT, # auth_verifier.auth_type
483                            # auth_verifier.auth_level:
484                            RPC_C_AUTHN_LEVEL_CONNECT,
485                            len_padding, # auth_verifier.auth_pad_length
486                            0, # auth_verifier.auth_reserved
487                            1 # auth_verifier.auth_context_id
488                            )
489         self.size = len(header_data) + ntlm_payload_size + len_padding
490         self.data = header_data + self.ntlm_payload + padding
491
492
493 # auth_3 PDU
494 class RPCAuth3OutPacket(object):
495     def __init__(self, logger=None):
496         self.logger = logger
497
498         self.size = 0
499         self.data = None
500
501         self.pfc_flags = PFC_FIRST_FRAG | PFC_LAST_FRAG
502         self.call_id = 1
503
504         self.ntlm_payload = None
505
506     def make(self):
507         if self.data is None:
508             self._make_packet_data()
509
510         return self.data
511
512     def _make_packet_data(self):
513         if self.ntlm_payload is None:
514             raise ValueError("'ntlm_payload' attribute must not be None")
515
516         ntlm_payload_size = len(self.ntlm_payload)
517         align_modulo = ntlm_payload_size % 4
518         if align_modulo > 0:
519             len_padding = (4 - align_modulo)
520         else:
521             len_padding = 0
522
523         header_data = pack("<bbbbbbbbhhl 4s bbbbl",
524                            5, 0, # rpc_vers, rpc_vers_minor
525                            DCERPC_PKT_AUTH_3, # ptype
526                            # pfc_flags
527                            self.pfc_flags,
528                            # drep: RPC spec chap14.htm (Data Representation Format Label)
529                            (1 << 4) | 0, 0, 0, 0,
530                            (28 + ntlm_payload_size + len_padding), # frag_length
531                            ntlm_payload_size + len_padding, # auth_length
532                            self.call_id, # call_id
533
534                            ## auth 3 specific:
535                            "",
536
537                            # sec_trailer:
538                            RPC_C_AUTHN_WINNT, # auth_verifier.auth_type
539                            # auth_verifier.auth_level:
540                            RPC_C_AUTHN_LEVEL_CONNECT,
541                            len_padding, # auth_verifier.auth_pad_length
542                            0, # auth_verifier.auth_reserved
543                            1 # auth_verifier.auth_context_id
544                            )
545         self.size = len(header_data) + ntlm_payload_size + len_padding
546         self.data = header_data + self.ntlm_payload + len_padding * "\x00"
547
548
549 # ping PDU
550 class RPCPingOutPacket(object):
551     def __init__(self, logger=None):
552         self.logger = logger
553
554         self.size = 0
555         self.data = None
556
557         self.pfc_flags = PFC_FIRST_FRAG | PFC_LAST_FRAG
558         self.call_id = 1
559
560     def make(self):
561         if self.data is None:
562             self._make_packet_data()
563
564         return self.data
565
566     def _make_packet_data(self):
567         header_data = pack("<bbbbbbbbhhl",
568
569                            ## common headers:
570                            5, 0, # rpc_vers, rpc_vers_minor
571                            DCERPC_PKT_PING, # ptype
572                            # pfc_flags
573                            self.pfc_flags,
574                            # drep: RPC spec chap14.htm (Data Representation Format Label)
575                            (1 << 4) | 0, 0, 0, 0,
576                            16, # frag_length
577                            0, # auth_length
578                            self.call_id # call_id
579                            )
580         self.size = len(header_data)
581         self.data = header_data
582
583
584 # rts PDU
585 class RPCRTSOutPacket(object):
586     def __init__(self, logger=None):
587         self.logger = logger
588         self.size = 0
589
590         # RTS packets
591         self.flags = RTS_FLAG_NONE
592         self.command_data = []
593
594     def make(self):
595         if self.command_data is None:
596             raise RTSParsingException("packet already returned")
597
598         self._make_header()
599
600         data = "".join(self.command_data)
601         data_size = len(data)
602
603         if (data_size != self.size):
604             raise RTSParsingException("sizes do not match: declared = %d,"
605                                       " actual = %d" % (self.size, data_size))
606         self.command_data = None
607
608         if self.logger is not None:
609             self.logger.info("returning packet: %s" % repr(data))
610
611         return data
612
613     def _make_header(self):
614         header_data = pack("<bbbbbbbbhhlhh",
615                            5, 0, # rpc_vers, rpc_vers_minor
616                            DCERPC_PKT_RTS, # ptype
617                            PFC_FIRST_FRAG | PFC_LAST_FRAG, # pfc_flags
618                            # drep: RPC spec chap14.htm (Data Representation Format Label)
619                            (1 << 4) | 0, 0, 0, 0,
620                            (20 + self.size), # frag_length
621                            0, # auth_length
622                            0, # call_id
623                            self.flags,
624                            len(self.command_data))
625         self.command_data.insert(0, header_data)
626         self.size = self.size + 20
627
628     def add_command(self, command_type, *args):
629         if command_type < 0 or command_type > 15:
630             raise RTSParsingException("command type unknown: %d (%s)" %
631                                       (command_type, str(type(command_type))))
632
633         self.size = self.size + 4
634
635         values = [pack("<l", command_type)]
636
637         command_size = RTS_CMD_SIZES[command_type]
638         if command_size > 4:
639             if command_type == RTS_CMD_FLOW_CONTROL_ACK:
640                 data = self._make_command_flow_control_ack(args[0])
641             elif (command_type == RTS_CMD_COOKIE
642                   or command_type == RTS_CMD_ASSOCIATION_GROUP_ID):
643                 data = self._make_command_cookie(args[0])
644             elif command_type == RTS_CMD_PADDING:
645                 data = self._make_command_padding_data(args[0])
646             elif command_type == RTS_CMD_CLIENT_ADDRESS:
647                 data = self._make_command_client_address(args[0])
648             else:
649                 # command with int32 value
650                 data = pack("<l", args[0])
651                 self.size = self.size + 4
652             values.append(data)
653
654         self.command_data.append("".join(values))
655         
656     def _make_command_flow_control_ack(self, data_blob):
657         # dumb method
658         len_data = len(data_blob)
659         if len_data != 24:
660             raise RTSParsingException("expected a length of %d bytes,"
661                                       " received %d" % (24, len_data))
662         self.size = self.size + len_data
663
664         return data_blob
665     
666     def _make_command_cookie(self, data_blob):
667         # dumb method
668         len_data = len(data_blob)
669         if len_data != 16:
670             raise RTSParsingException("expected a length of %d bytes,"
671                                       " received %d" % (16, len_data))
672         self.size = self.size + len_data
673
674         return data_blob
675
676     def _make_command_padding_data(self, data_blob):
677         len_data = len(data_blob)
678         data = pack("<l", len_data) + data_blob
679         self.size = self.size + 4 + len_data
680
681         return data
682
683     def _make_command_client_address(self, data_blob):
684         len_data = len(data_blob)
685         if len_data == 4:
686             address_type = 0 # ipv4
687         elif len_data == 16:
688             address_type = 1 # ipv6
689         else:
690             raise RTSParsingException("cannot deduce address type from data"
691                                       " length: %d" % len_data)
692
693         data = pack("<l", address_type) + data_blob + 12 * chr(0)
694         self.size = self.size + 4 + len_data + 12
695
696         return data