Package Peach :: Package Generators :: Module stun
[hide private]

Source Code for Module Peach.Generators.stun

  1   
  2  ''' 
  3  [BETA] STUN protocol generator 
  4   
  5  @author: Michael Eddington 
  6  @version: $Id: Peach.Generators.stun-pysrc.html 1138 2008-08-16 19:39:03Z meddingt $ 
  7  ''' 
  8   
  9  # 
 10  # Copyright (c) 2006-2007 Michael Eddington 
 11  # 
 12  # Permission is hereby granted, free of charge, to any person obtaining a copy  
 13  # of this software and associated documentation files (the "Software"), to deal 
 14  # in the Software without restriction, including without limitation the rights  
 15  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell  
 16  # copies of the Software, and to permit persons to whom the Software is  
 17  # furnished to do so, subject to the following conditions: 
 18  # 
 19  # The above copyright notice and this permission notice shall be included in     
 20  # all copies or substantial portions of the Software. 
 21  # 
 22  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  
 23  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  
 24  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE  
 25  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  
 26  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 27  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
 28  # SOFTWARE. 
 29  # 
 30   
 31  # Authors: 
 32  #   Michael Eddington (mike@phed.org) 
 33   
 34  # $Id: Peach.Generators.stun-pysrc.html 1138 2008-08-16 19:39:03Z meddingt $ 
 35   
 36  import re, struct, os 
 37  from Peach.generator import Generator 
 38  from Peach.Generators  import * 
 39  from Peach.Generators.dictionary  import * 
 40  from Peach.Generators.static  import * 
 41  from Peach.Generators.static  import Int8, Int16, Int32 
 42   
 43  #__all__ = ['StunAttribute', 
 44  #                  'StunHeader', 
 45  #                  'StunPayload', 
 46  #                  'StunPacket', 
 47  #                  'StunMappedAddressAttribute', 
 48  #                  'StunResponseAddressAttribute', 
 49  #                  'StunChangedAddressAttribute', 
 50  #                  'StunChangeRequestAttribute', 
 51  #                  'StunSourceAddressAttribute', 
 52  #                  'StunUsernameAttribute', 
 53  #                  'StunPasswordAttribute', 
 54  #                  'StunMessageIntegrityAttribute', 
 55  #                  'StunErrorCodeAttribute', 
 56  #                  'StunUnknownAttribute', 
 57  #                  'StunReflectedFromAttribute', 
 58  #                  'StunTransactionId'] 
 59   
60 -class StunPayload(block.Block2):
61 ''' 62 This generator creates a STUN payload. A payload 63 is a group of attributes 64 ''' 65 66 pass
67 68
69 -class StunPacket(Generator):
70 ''' 71 This generator creats a STUN packet 72 ''' 73 74 _header = None 75 _payload = None 76
77 - def __init__(self, group, header, payload):
78 ''' 79 @type group: Group 80 @param group: Group to use 81 @type header: StunHeader 82 @param header: Header generator 83 @type payload: StunPayload 84 @param payload: Payload generator 85 ''' 86 self.setGroup(group) 87 self._header = header 88 self._payload = payload
89
90 - def next(self):
91 done = 1 92 93 try: 94 self._header.next() 95 done = 0 96 except generator.GeneratorCompleted: 97 pass 98 99 try: 100 self._payload.next() 101 done = 0 102 except generator.GeneratorCompleted: 103 pass 104 105 if done: 106 raise generator.GeneratorCompleted("StunPacket")
107
108 - def getRawValue(self):
109 return self._header.getValue() + self._payload.getValue()
110
111 - def getHeader(self):
112 return self._header
113
114 - def setHeader(self, header):
115 self._header = header
116
117 - def getPayload(self):
118 return self._payload
119
120 - def setPayload(self, payload):
121 self._payload = payload
122 123
124 -class StunAttribute(SimpleGenerator):
125 ''' 126 This generator creates a STUN attribute. 127 ''' 128 129 MAPPED_ADDRESS = 0x0001 130 RESPONSE_ADDRESS = 0x0002 131 CHANGE_REQUEST = 0x0003 132 SOURCE_ADDRESS = 0x0004 133 CHANGED_ADDRESS = 0x0005 134 USERNAME = 0x0006 135 PASSWORD = 0x0007 136 MESSAGE_INTEGRITY = 0x0008 137 ERROR_CODE = 0x0009 138 UNKNOWN_ATTRIBUTES = 0x000a 139 REFLECTED_FROM = 0x000b 140 141 _type = None # 2 bytes 142 _length = None # 2 bytes 143 _generator = None 144
145 - def __init__(self, group, type, length, value):
146 ''' 147 @type group: Group 148 @param group: Group to use 149 @type type: integer 150 @param type: Attribute type 151 @type length: integer 152 @param length: If None length is auto based on value 153 @type value: Generator 154 @param value: Attribute value 155 ''' 156 self.setGroup(group) 157 if type != None: 158 self._type = type 159 if length != None: 160 self._length = length 161 if value != None: 162 self._generator = value
163
164 - def _getValue(self):
165 ''' 166 Override me! 167 ''' 168 return self._generator.getValue()
169
170 - def getRawValue(self):
171 value = self._getValue() 172 173 if self._length == None: 174 length = len(value) 175 else: 176 length = self._length 177 178 return Int16(self._type, 0, 0).getValue() + Int16(length, 0, 0).getValue() + value
179 180
181 -class StunMappedAddressAttribute(StunAttribute):
182 ''' 183 MAPPED-ADDRESS 184 185 The MAPPED-ADDRESS attribute indicates the mapped IP address and 186 port. It consists of an eight bit address family, and a sixteen bit 187 port, followed by a fixed length value representing the IP address. 188 189 0 1 2 3 190 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 191 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 192 |x x x x x x x x| Family | Port | 193 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 194 | Address | 195 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 196 197 The port is a network byte ordered representation of the mapped port. 198 The address family is always 0x01, corresponding to IPv4. The first 199 8 bits of the MAPPED-ADDRESS are ignored, for the purposes of 200 aligning parameters on natural boundaries. The IPv4 address is 32 201 bits. 202 ''' 203 204 _empty = 0 # 1 byte 205 _family = 0x01 # 1 byte 206 _port = None # 2 bytes 207 _address = None # 3 bytes 208
209 - def __init__(self, group, port, address, family = Static('\01')):
210 ''' 211 @type group: Group 212 @param group: Group to use 213 @type family: Generator 214 @param family: Family (defaults to 0x01 IPV4) 215 @type port: Generator 216 @param port: Port number 217 @type address: Generator 218 @param address: 32bit IPv4 address 219 ''' 220 self.setGroup(group) 221 if port != None: 222 self._port = port 223 if address != None: 224 self._address = address 225 if family != None: 226 self._family = family 227 self._type = StunAttribute.MAPPED_ADDRESS
228
229 - def _getValue(self):
230 return "%s%s%s" % ( self._family.getValue(), 231 self._port.getValue(), 232 self._address.getValue() )
233
234 - def reset(self):
235 self._port.reset() 236 self._address.reset() 237 self._family.reset()
238
239 - def next(self):
240 done = 1 241 242 try: 243 self._family.next() 244 done = 0 245 except generator.GeneratorCompleted: 246 pass 247 248 try: 249 self._port.next() 250 done = 0 251 except generator.GeneratorCompleted: 252 pass 253 254 try: 255 self._address.next() 256 done = 0 257 except generator.GeneratorCompleted: 258 pass 259 260 if done: 261 raise generator.GeneratorCompleted("StunMappedAddressAttribute")
262 263
264 -class StunResponseAddressAttribute(StunMappedAddressAttribute):
265 ''' 266 RESPONSE-ADDRESS 267 268 The RESPONSE-ADDRESS attribute indicates where the response to a 269 Binding Request should be sent. Its syntax is identical to MAPPED- 270 ADDRESS. 271 ''' 272
273 - def __init__(self, group, port, address, family = 0x01):
274 ''' 275 @type group: Group 276 @param group: Group to use 277 @type family: Generator 278 @param family: Family (defaults to 0x01 IPV4) 279 @type port: Generator 280 @param port: Port number 281 @type address: Generator 282 @param address: 32bit IPv4 address 283 ''' 284 StunMappedAddressAttribute.__init__(self, group, port, address, family) 285 self._type = StunAttribute.RESPONSE_ADDRESS
286 287
288 -class StunChangedAddressAttribute(StunMappedAddressAttribute):
289 ''' 290 CHANGED-ADDRESS 291 292 The CHANGED-ADDRESS attribute indicates the IP address and port where 293 responses would have been sent from if the "change IP" and "change 294 port" flags had been set in the CHANGE-REQUEST attribute of the 295 Binding Request. The attribute is always present in a Binding 296 Response, independent of the value of the flags. Its syntax is 297 identical to MAPPED-ADDRESS. 298 ''' 299
300 - def __init__(self, group, port, address, family = 0x01):
301 ''' 302 @type group: Group 303 @param group: Group to use 304 @type family: Generator 305 @param family: Family (defaults to 0x01 IPV4) 306 @type port: Generator 307 @param port: Port number 308 @type address: Generator 309 @param address: 32bit IPv4 address 310 ''' 311 StunMappedAddressAttribute.__init__(self, group, port, address, family) 312 self._type = StunAttribute.CHANGED_ADDRESS
313 314
315 -class StunChangeRequestAttribute(StunAttribute):
316 ''' 317 CHANGE-REQUEST 318 319 The CHANGE-REQUEST attribute is used by the client to request that 320 the server use a different address and/or port when sending the 321 response. The attribute is 32 bits long, although only two bits (A 322 and B) are used: 323 324 0 1 2 3 325 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 326 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 327 |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 A B 0| 328 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 329 330 The meaning of the flags is: 331 332 A: This is the "change IP" flag. If true, it requests the server 333 to send the Binding Response with a different IP address than the 334 one the Binding Request was received on. 335 336 B: This is the "change port" flag. If true, it requests the 337 server to send the Binding Response with a different port than the 338 one the Binding Request was received on. 339 ''' 340 341 CHANGE_IP = 4 342 CHANGE_PORT = 2 343 344 _value = None 345 _changeIp = None 346 _changePort = None 347
348 - def __init__(self, group, changeIp, changePort, value = None):
349 ''' 350 @type group: Group 351 @param group: Group to use 352 @type changeIp: Generator 353 @param changeIp: changeIp value (0 or 4) 354 @type changePort: Generator 355 @param changePort: changePort value (0 or 2) 356 @type value: Generator 357 @param value: 32bit value (optional) 358 ''' 359 self.setGroup(group) 360 if changeIp != None: 361 self._changeIp = changeIp 362 if changePort != None: 363 self._changePort = changePort 364 if value != None: 365 self._value = value 366 self._type = StunAttribute.CHANGE_REQUEST
367
368 - def _getValue(self):
369 if self._value == None: 370 value = int(self._changeIp.getValue()) | int(self._changePort.getValue()) 371 372 return Int32(value, 0, 0).getValue()
373
374 - def next(self):
375 done = 1 376 377 try: 378 self._changeIp.next() 379 done = 0 380 except generator.GeneratorCompleted: 381 pass 382 383 try: 384 self._changePort.next() 385 done = 0 386 except generator.GeneratorCompleted: 387 pass 388 389 if self._value: 390 try: 391 self._value.next() 392 done = 0 393 except generator.GeneratorCompleted: 394 pass 395 396 if done: 397 raise generator.GeneratorCompleted("StunChangeRequestAttribute")
398 399
400 -class StunSourceAddressAttribute(StunMappedAddressAttribute):
401 ''' 402 SOURCE-ADDRESS 403 404 The SOURCE-ADDRESS attribute is present in Binding Responses. It 405 indicates the source IP address and port that the server is sending 406 the response from. Its syntax is identical to that of MAPPED- 407 ADDRESS. 408 ''' 409
410 - def __init__(self, group, port, address, family = 0x01):
411 ''' 412 @type group: Group 413 @param group: Group to use 414 @type family: Generator 415 @param family: Family (defaults to 0x01 IPV4) 416 @type port: Generator 417 @param port: Port number 418 @type address: Generator 419 @param address: 32bit IPv4 address 420 ''' 421 StunMappedAddressAttribute.__init__(self, group, port, address, family) 422 self._type = StunAttribute.SOURCE_ADDRESS
423 424
425 -class StunUsernameAttribute(StunAttribute):
426 ''' 427 USERNAME 428 429 The USERNAME attribute is used for message integrity. It serves as a 430 means to identify the shared secret used in the message integrity 431 check. The USERNAME is always present in a Shared Secret Response, 432 along with the PASSWORD. It is optionally present in a Binding 433 Request when message integrity is used. 434 435 The value of USERNAME is a variable length opaque value. Its length 436 MUST be a multiple of 4 (measured in bytes) in order to guarantee 437 alignment of attributes on word boundaries. 438 ''' 439
440 - def __init__(self, group, username):
441 ''' 442 @type group: Group 443 @param group: Group to use 444 @type username: Generator 445 @param username: Username 446 ''' 447 self.setGroup(group) 448 if username != None: 449 self._value = username 450 self._type = StunAttribute.USERNAME
451 452
453 -class StunPasswordAttribute(StunAttribute):
454 ''' 455 PASSWORD 456 457 The PASSWORD attribute is used in Shared Secret Responses. It is 458 always present in a Shared Secret Response, along with the USERNAME. 459 460 The value of PASSWORD is a variable length value that is to be used 461 as a shared secret. Its length MUST be a multiple of 4 (measured in 462 bytes) in order to guarantee alignment of attributes on word 463 boundaries. 464 ''' 465
466 - def __init__(self, group, password):
467 ''' 468 @type group: Group 469 @param group: Group to use 470 @type password: Generator 471 @param password: Password 472 ''' 473 self.setGroup(group) 474 if value != None: 475 self._value = username 476 self._type = StunAttribute.PASSWORD
477 478
479 -class StunMessageIntegrityAttribute(StunAttribute):
480 ''' 481 MESSAGE-INTEGRITY 482 483 The MESSAGE-INTEGRITY attribute contains an HMAC-SHA1 [13] of the 484 STUN message. It can be present in Binding Requests or Binding 485 Responses. Since it uses the SHA1 hash, the HMAC will be 20 bytes. 486 The text used as input to HMAC is the STUN message, including the 487 header, up to and including the attribute preceding the MESSAGE- 488 INTEGRITY attribute. That text is then padded with zeroes so as to be 489 a multiple of 64 bytes. As a result, the MESSAGE-INTEGRITY attribute 490 MUST be the last attribute in any STUN message. The key used as 491 input to HMAC depends on the context. 492 ''' 493
494 - def __init__(self, group, hmac):
495 ''' 496 @type group: Group 497 @param group: Group to use 498 @type hmac: Generator 499 @param hmac: HMAC-SHA1 500 ''' 501 self.setGroup(group) 502 if value != None: 503 self._value = hmac 504 self._type = StunAttribute.MESSAGE_INTEGRITY
505 506
507 -class StunErrorCodeAttribute(StunAttribute):
508 ''' 509 ERROR-CODE 510 511 The ERROR-CODE attribute is present in the Binding Error Response and 512 Shared Secret Error Response. It is a numeric value in the range of 513 100 to 699 plus a textual reason phrase encoded in UTF-8, and is 514 consistent in its code assignments and semantics with SIP [10] and 515 HTTP [15]. The reason phrase is meant for user consumption, and can 516 be anything appropriate for the response code. The lengths of the 517 reason phrases MUST be a multiple of 4 (measured in bytes). This can 518 be accomplished by added spaces to the end of the text, if necessary. 519 Recommended reason phrases for the defined response codes are 520 presented below. 521 522 To facilitate processing, the class of the error code (the hundreds 523 digit) is encoded separately from the rest of the code. 524 525 0 1 2 3 526 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 527 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 528 | 0 |Class| Number | 529 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 530 | Reason Phrase (variable) .. 531 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 532 533 The class represents the hundreds digit of the response code. The 534 value MUST be between 1 and 6. The number represents the response 535 code modulo 100, and its value MUST be between 0 and 99. 536 537 The following response codes, along with their recommended reason 538 phrases (in brackets) are defined at this time: 539 540 400 (Bad Request): The request was malformed. The client should not 541 retry the request without modification from the previous 542 attempt. 543 544 401 (Unauthorized): The Binding Request did not contain a MESSAGE- 545 INTEGRITY attribute. 546 547 420 (Unknown Attribute): The server did not understand a mandatory 548 attribute in the request. 549 550 430 (Stale Credentials): The Binding Request did contain a MESSAGE- 551 INTEGRITY attribute, but it used a shared secret that has 552 expired. The client should obtain a new shared secret and try 553 again. 554 555 431 (Integrity Check Failure): The Binding Request contained a 556 MESSAGE-INTEGRITY attribute, but the HMAC failed verification. 557 This could be a sign of a potential attack, or client 558 implementation error. 559 560 432 (Missing Username): The Binding Request contained a MESSAGE- 561 INTEGRITY attribute, but not a USERNAME attribute. Both must be 562 present for integrity checks. 563 564 433 (Use TLS): The Shared Secret request has to be sent over TLS, but 565 was not received over TLS. 566 567 500 (Server Error): The server has suffered a temporary error. The 568 client should try again. 569 570 600 (Global Failure:) The server is refusing to fulfill the request. 571 The client should not retry. 572 ''' 573 574 pass
575
576 -class StunUnknownAttribute(StunAttribute):
577 pass
578
579 -class StunReflectedFromAttribute(StunMappedAddressAttribute):
580 ''' 581 REFLECTED-FROM 582 583 The REFLECTED-FROM attribute is present only in Binding Responses, 584 when the Binding Request contained a RESPONSE-ADDRESS attribute. The 585 attribute contains the identity (in terms of IP address) of the 586 source where the request came from. Its purpose is to provide 587 traceability, so that a STUN server cannot be used as a reflector for 588 denial-of-service attacks. 589 590 Its syntax is identical to the MAPPED-ADDRESS attribute 591 ''' 592
593 - def __init__(self, group, port, address, family = 0x01):
594 ''' 595 @type group: Group 596 @param group: Group to use 597 @type family: Generator 598 @param family: Family (defaults to 0x01 IPV4) 599 @type port: Generator 600 @param port: Port number 601 @type address: Generator 602 @param address: 32bit IPv4 address 603 ''' 604 StunMappedAddressAttribute.__init__(self, group, port, address, family) 605 self._type = StunAttribute.REFLECTED_FROM
606 607
608 -class StunTransactionId(Generator):
609 ''' 610 Generate a valid transaction id 611 ''' 612 613 _tid = None 614
615 - def __init__(self, group):
616 self.setGroup(group) 617 self._tid = self.getRandomTID()
618
619 - def getRandomTID(self):
620 return os.urandom(16)
621
622 - def next(self):
623 self._tid = self.getRandomTID()
624
625 - def getRawValue(self):
626 return self._tid
627
628 -class StunHeader(Generator):
629 ''' 630 This generator creates a STUN header. A STUN header 631 is 20 bytes long. 632 ''' 633 634 BINDING_REQUEST = 0x0001 635 BINDING_RESPONSE = 0x0101 636 BINDING_ERROR_RESPONSE = 0x0111 637 SHARED_SECRET_REQUEST = 0x0002 638 SHARED_SECRET_RESPONSE = 0x0102 639 SHARED_SECRET_ERROR_RESPONSE = 0x0112 640 641 _messageType = None # 2 bytes 642 _length = None # 2 bytes 643 _transactionId = None # 18 bytes 644
645 - def __init__(self, group, messageType, transactionId, length):
646 ''' 647 @type group: Group 648 @param group: Group to use 649 @type messageType: Generator 650 @param messageType: Message type we are generating 651 @type transactionId: Generator 652 @param transactionId: Transaction Id 653 @type length: Generator 654 @param length: Length of STUN payload 655 ''' 656 657 self.setGroup(group) 658 if messageType != None: 659 self._messageType = messageType 660 if transactionId != None: 661 self._transactionId = transactionId 662 if length != None: 663 self._length = length
664
665 - def getRawValue(self):
666 return Int16(self._messageType.getValue(), 0, 0) + Int16(self._length.getValue(), 0, 0).getValue() + self._transactionId.getValue()
667
668 - def reset(self):
669 pass
670
671 - def unittest():
672 pass
673 unittest = staticmethod(unittest)
674 675 676 677 # end 678