Full Code of x56/airpyrt-tools for AI

main 64f526b3e0a9 cached
19 files
77.6 KB
23.5k tokens
148 symbols
1 requests
Download .txt
Repository: x56/airpyrt-tools
Branch: main
Commit: 64f526b3e0a9
Files: 19
Total size: 77.6 KB

Directory structure:
gitextract_nzg9t11t/

├── .gitignore
├── LICENSE
├── README.md
├── acp/
│   ├── __init__.py
│   ├── __main__.py
│   ├── basebinary.py
│   ├── cflbinary.py
│   ├── cli.py
│   ├── clibs/
│   │   ├── AppleSRP.py
│   │   └── __init__.py
│   ├── client.py
│   ├── encryption.py
│   ├── exception.py
│   ├── keystream.py
│   ├── message.py
│   ├── misc.py
│   ├── property.py
│   └── session.py
└── setup.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.DS_Store

*.pyc
*.egg-info
build/
dist/


================================================
FILE: LICENSE
================================================
Copyright (c) 2016 Vince Cali

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

================================================
FILE: README.md
================================================
# AirPyrt Tools

### License

See LICENSE


### Requirements

- python 2.7
- pycrypto


### Installation

`python setup.py install [--user]`


### Usage

`python [-B] -m acp`

    usage: __main__.py [-h] [-t address] [-p password] [--listprop]
                       [--helpprop property] [--getprop property]
                       [--setprop property value] [--dumpprop] [--acpprop]
                       [--dump-syslog] [--reboot] [--factory-reset]
                       [--flash-primary firmware_path] [--do-feat-command]
                       [--decrypt inpath outpath] [--extract inpath outpath]
                       [--srp-test]

    optional arguments:
      -h, --help            show this help message and exit

    AirPort client parameters:
      -t address, --target address
                            IP address or hostname of the target router
      -p password, --password password
                            router admin password

    AirPort client commands:
      --listprop            list supported properties
      --helpprop property   print the description of the specified property
      --getprop property    get the value of the specified property
      --setprop property value
                            set the value of the specified property
      --dumpprop            dump values of all supported properties
      --acpprop             get acp acpprop list
      --dump-syslog         dump the router system log
      --reboot              reboot device
      --factory-reset       RESET EVERYTHING and reboot; you have been warned!
      --flash-primary firmware_path
                            flash primary partition firmware
      --do-feat-command     send 0x1b (feat) command

    Basebinary commands:
      --decrypt inpath outpath
                            decrypt the basebinary
      --extract inpath outpath
                            extract the gzimg contents

    Test arguments:
      --srp-test            SRP (requires OS X)


### Notes

**IMPORTANT**

This still uses the old ACP protocol implementation, which puts the admin password
of the device over the wire in a trivially recoverable format. This was fixed by 
in the new protocol which uses SRP authentication and better encryption of requests
to/from the device. Until this is implemented this tool is entirely unsafe to use,
especially for remote administration (which you should have disabled anyway...).

This project grew organically out of my understanding of various pieces of the ACP 
protocol. I've restructured the code a few times as it has improved, but there are 
still many gaps in the implementation, and a lot of code smell. Between sitting on
this indefinitely making incremental improvements (and probably never releasing a 
"finished" product) and releasing it in a rougher state for others to explore, the
latter made far more sense.

Return value of 0xfffffff6 when using --getprop means the property is not avaliable/readable


## TODO (very incomplete list in no particular order)

- add IP address type for properties, make sure it supports IPv4 and IPv6
- specify RO/WO/RW attribute for properties
- exception handling:
  - invalid struct fields aren't handled well in many cases
  - finish adding custom exception classes and make sure we're using them
- logging (mostly done, still looks horrible) with verbosity controls
- review and update docstrings
- SRP support (fix pysrp because ctypes hax, while fun, are horrible and non-portable)
- ACP protocol version 2 (full session encryption)
- handle encrypted property elements
- basebinary repacking/reencryption
- basebinary rootfs mounting
- threaded server
- handle protocol v1 (for old firmwares/devices)
- bonjour announcement/discovery
- options to specify no encryption, old method, and new (SRP) method
- ACPMonitorSession support
- ACPRPC support


================================================
FILE: acp/__init__.py
================================================


================================================
FILE: acp/__main__.py
================================================
import cli
cli.main()


================================================
FILE: acp/basebinary.py
================================================
import logging
import os.path
import struct
import zlib

from Crypto.Cipher import AES

#XXX: ugh...
from .misc import cast_u32

# hardcoded 128 bit values
# 107: 52 49 C3 51 02 8B F1 FD 2B D1 84 9E 28 B2 3F 24
# 108: BB 7D EB 09 70 D8 EE 2E 00 FA 46 CB 1C 3C 09 8E
# 115: 10 75 E8 06 F4 77 0C D4 76 3B D2 85 A6 4E 91 74
# 120: 68 8C DD 3B 1B 6B DD A2 07 B6 CE C2 73 52 92 D2

_basebinary_keys = {
	#3   : "",
	#102 : "",
	#104 : "",
	#105 : "",
	#106 : "",
	107 : "5249c351028bf1fd2bd1849e28b23f24",
	108 : "bb7deb0970d8ee2e00fa46cb1c3c098e",
	#109 : "",
	#113 : "",
	#114 : "",
	115 : "1075e806f4770cd4763bd285a64e9174",
	#116 : "",
	#117 : "",
	#119 : "",
	120 : "688cdd3b1b6bdda207b6cec2735292d2",
	}

def _derive_key(model):
	if model not in _basebinary_keys:
		return None
	key = _basebinary_keys[model].decode("hex")
	derived_key = ""
	for i in range(len(key)):
		derived_key += chr(ord(key[i]) ^ (i + 0x19))
	logging.debug("derived key {0}".format(derived_key.encode("hex")))
	return derived_key


class BasebinaryError(Exception):
	pass

class Basebinary(object):
	_header_magic = "APPLE-FIRMWARE\x00"
	#XXX: do we need to fix shitty Python struct member signdness things here too?
	_header_format = struct.Struct(">15sB2I4BI")
	
	header_size = _header_format.size
	
	
	@classmethod
	def parse(cls, data):
		if len(data) < (cls.header_size + 4):
			raise BasebinaryError("not enough data to parse")
		
		header_data = data[:cls.header_size]
		inner_data = data[cls.header_size:-4]
		
		#XXX: and here??
		stored_checksum, = struct.unpack(">I", data[-4:])
		
		(byte_0x0F, model, version, byte_0x18, byte_0x19, byte_0x1A, flags, unk_0x1C) = cls.parse_header(header_data)
		
		if flags & 2:
			inner_data = cls.decrypt(inner_data, model, byte_0x0F)
		
		#XXX: why is Python so shitty about this comparison <.<
		checksum = cast_u32(zlib.adler32(header_data+inner_data))
		logging.debug("stored checksum     {0:#x}".format(stored_checksum))
		logging.debug("calculated checksum {0:#x}".format(checksum))
		logging.debug("data length         {0:#x}".format(len(header_data+inner_data)))
		if stored_checksum != checksum:
			raise BasebinaryError("bad checksum")
			
		return inner_data
	
	
	@classmethod
	def compose(cls, data):
		#TODO
		pass
	
	
	@classmethod
	def parse_header(cls, data):
		magic, byte_0x0F, model, version, byte_0x18, byte_0x19, byte_0x1A, flags, unk_0x1C = cls._header_format.unpack(data)
		
		if magic != cls._header_magic:
			raise BasebinaryError("bad header magic")
		
		return (byte_0x0F, model, version, byte_0x18, byte_0x19, byte_0x1A, flags, unk_0x1C)
	
	
	@classmethod
	def compose_header(cls, byte_0x0F, model, version, byte_0x18, byte_0x19, byte_0x1A, flags, unk_0x1C):
		#TODO
		pass
	
	
	@classmethod
	def decrypt(cls, data, model, byte_0x0F):
		iv = cls._header_magic+chr(byte_0x0F)
		key = _derive_key(model)
		if key is None:
			raise BasebinaryError("key missing for model {0}".format(model))
		
		decrypted_data = ""
		remaining_length = len(data)
		chunk_length = 0x8000
		while remaining_length:
			if remaining_length > chunk_length:
				decrypted_data   += cls.decrypt_chunk(data[-remaining_length:-(remaining_length-chunk_length)], key, iv)
				remaining_length -= chunk_length
			else:
				decrypted_data   += cls.decrypt_chunk(data[-remaining_length:], key, iv)
				remaining_length = 0
		
		return decrypted_data
	
	
	@classmethod
	def decrypt_chunk(cls, encrypted_data, key, iv):
		cipher = AES.new(key, AES.MODE_CBC, iv)
		decrypted_data = ""
		bytes_left = len(encrypted_data)
		while bytes_left:
			#logging.debug("bytes left: {0:#x}".format(bytes_left))
			if bytes_left > 0x10:
				decrypted_data += cipher.decrypt(encrypted_data[-bytes_left:-(bytes_left-0x10)])
				bytes_left -= 0x10
			elif bytes_left == 0x10:
				decrypted_data += cipher.decrypt(encrypted_data[-bytes_left:])
				bytes_left = 0
			else: # bytes_left < 0x10
				#LOL: odd-sized chunk at the end is left unencrypted
				decrypted_data += encrypted_data[-bytes_left:]
				bytes_left = 0
		
		return decrypted_data
	
	
	@classmethod
	def encrypt(cls, data):
		#TODO: not really necessary; router doesn't require an encrypted firmware if flag is unset
		pass
	
	
	@classmethod
	def extract(cls, data):
		#TODO: proper gzip header validation?
		gzip_offset = data.index("\x1f\x8b\x08")
		gzdata = data[gzip_offset:]
		
		return zlib.decompress(gzdata, 16+zlib.MAX_WBITS)


================================================
FILE: acp/cflbinary.py
================================================
import logging
import struct
from collections import OrderedDict
from math  import log
from types import *


_header_magic = "CFB0"
_footer_magic = "END!"
_header_size = len(_header_magic)
_footer_size = len(_footer_magic)

def _lslice(data, length):
	""" Slice object into two counting from the left
	
	Returns:
		(left_slice, right_slice)
	"""
	return data[:length], data[length:]


class CFLBinaryPListComposeError(Exception):
	"""Exception raised for errors composing a cflbinary format property list"""
	pass

class CFLBinaryPListParseError(Exception):
	"""Exception raised for errors parsing a cflbinary format property list"""
	pass


class CFLBinaryPListComposer(object):
	"""Write cflbinary format property list"""
	
	@classmethod
	def _pack_object(cls, obj):
		""" Pack a supported Python built-in object
		
		Note:
			this function is super hacky and needs to be fixed
		
		Returns:
			data
		
		Raises:
			CFLBinaryPListComposeError
		"""
		data = ""
		
		object_type = type(obj)
		
		if	 object_type == NoneType:
			data += "\x00"
		
		elif object_type == BooleanType:
			if not obj:
				data += "\x08"
			else:
				data += "\x09"
		
		elif object_type == IntType:
			object_marker = 0x10
			buf = ""
			#XXX: need to actually catch unsupported packed sizes
			for fmt in [">B", ">H", ">I", ">Q"]:
				try:
					buf = struct.pack(fmt, obj)
				except struct.error:
					logging.debug("XXX: skipping {0}".format(fmt))
					pass
				else:
					break
			
			object_marker += int(log(len(buf), 2))
			
			data += chr(object_marker)
			data += buf
		
		elif object_type == FloatType:
			object_marker = 0x20
			buf = ""
			#XXX: need to actually catch unsupported packed sizes
			for fmt in ["!f", "!d"]:
				try:
					buf = struct.pack(fmt, obj)
				except struct.error:
					logging.debug("XXX: skipping {0}".format(fmt))
					pass
				else:
					break
			
			object_marker += int(log(len(buf), 2))
			
			data += chr(object_marker)
			data += buf
		
		#XXX: DateType?
		
		elif object_type == StringType:
			object_marker = 0x40
			data_len = len(obj)
			if data_len < 0xF:
				object_marker += data_len
				data += chr(object_marker)
			else:
				object_marker += 0xF
				data += chr(object_marker)
				data += cls._pack_object(data_len)
			data += obj
		
		elif object_type == UnicodeType:
			data += "\x70"
			data += obj.encode("utf-8")
			data += "\x00"
		
		elif object_type == ListType:
			data += "\xA0"
			for element in obj:
				data += cls._pack_object(element)
			data += "\x00"
		
		elif object_type in [DictType, OrderedDict]:
			data += "\xD0"
			for k, v in obj.iteritems():
				data += cls._pack_object(k)
				data += cls._pack_object(v)
			data += "\x00"
		
		else:
			raise CFLBinaryPListComposeError("unsupported Python built-in type: {0}".format(type(obj)))
		
		return data
	
	@classmethod
	def compose(cls, object):
		""" Compose Python object into equivalent plist
		
		Returns:
			plist_data
		
		Raises:
			CFLBinaryPListComposeError
		"""
		data =  _header_magic
		# assume one root object
		data += cls._pack_object(object)
		data += _footer_magic
		return data


class CFLBinaryPListParser(object):
	"""Read cflbinary format property list"""
	
	@classmethod
	def _unpack_int(cls, size_exponent, data):
		""" Unpack an int object as a Python int from the provided data
		
		Returns:
			(int, remaining_data)
		
		Raises:
			CFLBinaryPListParseError
		"""
		int_size = 2**size_exponent
		int_bytes, data = _lslice(data, int_size)
		#XXX: are these supposed to be signed or unsigned?
		if int_size == 1:
			int_fmt = ">B"
		elif int_size == 2:
			int_fmt = ">H"
		elif int_size == 4:
			int_fmt = ">I"
		elif int_size == 8:
			int_fmt = ">Q"
		else:
			raise CFLBinaryPListParseError("unsupported int packed object size of {0} bytes")
		
		try:
			(int_val, ) = struct.unpack(int_fmt, int_bytes)
		except struct.error:
			raise CFLBinaryPListParseError("failed to unpack int value")
		
		return int_val, data
	
	@classmethod
	def _unpack_real(cls, size_exponent, data):
		""" Unpack a real object as a Python float from the provided data
		
		Returns:
			(float, remaining_data)
		
		Raises:
			CFLBinaryPListParseError
		"""
		real_size = 2**size_exponent
		real_bytes, data = _lslice(data, real_size)
		if   real_size == 4:
			real_fmt = ">f"
		elif real_size == 8:
			real_fmt = ">d"
		else:
			raise CFLBinaryPListParseError("unsupported real packed object size of {0} bytes")
		
		try:
			(float_val, ) = struct.unpack(real_fmt, real_bytes)
		except struct.error:
			raise CFLBinaryPListParseError("failed to unpack float value")
		
		return float_val, data
	
	@classmethod
	def _unpack_count(cls, object_info, data):
		""" Unpack count from object info nibble and/or packed int value
		
		Returns:
			(count, remaining_data)
		
		Raises:
			CFLBinaryPListParseError
		"""
		if object_info == 0x0F:
			# count is the following packed int object
			marker, data = cls._unpack_object_marker(data)
			count_object_type = marker & 0xF0
			count_object_info = marker & 0x0F
			if count_object_type != 0x10:
				raise CFLBinaryPListParseError("expected count to be a packed int object")
			count, data = cls._unpack_int(count_object_info, data)
		else:
			count = object_info
		
		return count, data
	
	@classmethod
	def _unpack_object_marker(cls, data):
		""" Unpack an object marker from the provided data
		
		Returns:
			(marker, remaining_data)
		
		Raises:
			CFLBinaryPListParseError
		"""
		marker_byte, data = _lslice(data, 1)
		try:
			(marker, ) = struct.unpack(">B", marker_byte)
		except struct.error:
			raise CFLBinaryPListParseError("failed to unpack object marker")
		
		return marker, data
	
	@classmethod
	def _unpack_object(cls, data):
		""" Unpack an object from the provided data
		
		Returns:
			(obj, remaining_data)
		
		Raises:
			CFLBinaryPListParseError
		"""
		obj = None
		
		marker, data = cls._unpack_object_marker(data)
		object_type = marker & 0xF0
		object_info = marker & 0x0F
		
		if object_type == 0x00:
			if object_info == 0x00:   # null, null object
				return None, data
			elif object_info == 0x08: # bool, false
				return False, data
			elif object_info == 0x09: # bool, true
				return True, data
			else:
				raise CFLBinaryPListParseError("unsupported object info value for object type 0x00: {0:#x}".format(object_info))
		
		elif object_type == 0x10:     # int, big-endian
			return cls._unpack_int(object_info, data)
		
		elif object_type == 0x20:     # real, big-endian
			return cls._unpack_real(object_info, data)
		
		elif object_type == 0x30:     # date
			#XXX: not sure if this is actually used
			raise CFLBinaryPListParseError("date support not implemented")
		
		elif object_type == 0x40:     # data
			size, data = cls._unpack_count(object_info, data)
			#XXX: we return data as str type, is this ok?
			return _lslice(data, size)
		
		elif object_type == 0x50:     # string, ASCII
			raise CFLBinaryPListParseError("ASCII string support not implemented")
		
		elif object_type == 0x60:     # string, Unicode
			raise CFLBinaryPListParseError("Unicode string support not implemented")
		
		elif object_type == 0x70:     # string, UTF8, NULL terminated
			raw = ""
			while True:
				byte, data = _lslice(data, 1)
				if byte == "\x00":
					break
				raw += byte
			#XXX: what exceptions could we get here?
			obj = raw.decode("utf-8")
			return obj, data
		
		elif object_type == 0x80:      # uid
			raise CFLBinaryPListParseError("uid support not implemented")
		
		elif object_type == 0xA0:      # array
			obj = []
			while True:
				element, data = cls._unpack_object(data)
				if element == None:
					break
				obj.append(element)
			return obj, data
		
		elif object_type == 0xB0:      # ordset
			raise CFLBinaryPListParseError("ordset support not implemented")
		
		elif object_type == 0xC0:      # set
			raise CFLBinaryPListParseError("set support not implemented")
		
		elif object_type == 0xD0:      # dict
			keys = []
			values = []
			while True:
				key, data = cls._unpack_object(data)
				if key == None:
					break
				keys.append(key)
				
				value, data = cls._unpack_object(data)
				values.append(value)
			
			obj = OrderedDict()
			for i in range(len(keys)):
				obj[keys[i]] = values[i]
			
			return obj, data
		
		else:
			raise CFLBinaryPListParseError("unsupported object type: {0:#x}".format(object_type))
	
	@classmethod
	def parse(cls, data):
		""" Parse plist data into equivalent Python built-in object type
		
		Returns:
			obj
		
		Raises:
			CFLBinaryPListParseError
		"""
		# bail now if there isn't enough data for header, footer, and at least one object
		if len(data) < (_header_size + _footer_size + 1):
			raise CFLBinaryPListParseError("not enough data to parse")
		
		header_data, data = _lslice(data, _header_size)
		if header_data != _header_magic:
			raise CFLBinaryPListParseError("bad header magic")
		
		# read object stream (assume one root object)
		obj, remaining_data = cls._unpack_object(data)
		if len(remaining_data) > _footer_size:
			raise CFLBinaryPListParseError("extra data found after unpacking root object")
		
		if remaining_data != _footer_magic:
			raise CFLBinaryPListParseError("bad footer magic")
		
		return obj


================================================
FILE: acp/cli.py
================================================
import argparse
import logging
import os.path
import sys
import time

from collections import OrderedDict

from .basebinary import *
from .client import ACPClient
from .exception import *
from .property import ACPProperty


class _ArgParser(argparse.ArgumentParser):
	def error(self, message):
		sys.stderr.write("error: {0}\n".format(message))
		#self.print_help()
		sys.exit(2)


def _cmd_not_implemented(*unused):
	raise ACPCommandLineError("command handler not implemented")

def _cmd_listprop(unused):
	print "\nSupported properties:\n"
	prop_names = ACPProperty.get_supported_property_names()
	for name in prop_names:
		print "{0}: {1}".format(name, ACPProperty.get_property_info_string(name, "description"))
	print

def _cmd_helpprop(args):
	prop_name = args.pop()
	description = ACPProperty.get_property_info_string(prop_name, "description")
	prop_type = ACPProperty.get_property_info_string(prop_name, "type")
	validation = ACPProperty.get_property_info_string(prop_name, "validation")
	s = "{0} ({1}".format(description, prop_type)
	if validation:
		s += ", {0})".format(validation)
	else:
		s += ")"
	print s

def _cmd_getprop(client, args):
	prop_name = args.pop()
	prop = client.get_properties([prop_name])
	if len(prop):
		print ACPProperty(prop_name, prop[0].value)

def _cmd_setprop(client, args):
	prop_name, prop_value = args
	prop_type = ACPProperty.get_property_info_string(prop_name, "type")
	prop = ACPProperty()
	if prop_type == "dec":
		try:
			prop = ACPProperty(prop_name, int(prop_value))
		except ValueError:
			logging.error("value for \"{0}\" has the wrong type, should be {0}".format(prop_name, prop_type))
	elif prop_type == "hex":
		try:
			#XXX: this is not the right way to do exceptions
			prop = ACPProperty(prop_name, int(prop_value, 16))
		except ValueError:
			logging.error("value for \"{0}\" has the wrong type, should be {0}".format(prop_name, prop_type))
	elif prop_type == "mac":
		#XXX: not catching our exception
		prop = ACPProperty(prop_name, prop_value)
	elif prop_type == "bin":
		prop = ACPProperty(prop_name, prop_value.decode("hex"))
	elif prop_type == "str":
		prop = ACPProperty(prop_name, prop_value)
	elif prop_type in ["cfb", "log"]:
		logging.error("unsupported prop type: {0}".format(prop_type))
	client.set_properties({prop_name : prop})

def _cmd_dumpprop(client, unused):
	prop_names = ACPProperty.get_supported_property_names()
	properties = client.get_properties(prop_names)
	for prop in properties:
		padded_description = ACPProperty.get_property_info_string(prop.name, "description").ljust(32, " ")
		print "{0}: {1}".format(padded_description, prop)

def _cmd_acpprop(client, unused):
	props_reply = client.get_properties(["prop"])
	props_raw = props_reply[0].value
	props = ""
	for i in range(len(props_raw) / 4):
		props += "{0}\n".format(props_raw[i*4:i*4+4])
	print props

def _cmd_dump_syslog(client, unused):
	print "{0}".format(client.get_properties(["logm"])[0])

def _cmd_reboot(client, unused):
	print "Rebooting device"	
	client.set_properties({"acRB" : ACPProperty("acRB", 0)})

def _cmd_factory_reset(client, unused):
	print "Performing factory reset"	
	client.set_properties(OrderedDict([("acRF",ACPProperty("acRF", 0)), ("acRB",ACPProperty("acRB", 0))]))

def _cmd_flash_primary(client, args):
	fw_path = args.pop()
	if os.path.exists(fw_path):
		with open(fw_path, "rb") as fw_file:
			fw_data = fw_file.read()
		print "Flashing primary firmware partition"
		client.flash_primary(fw_data)
	else:
		logging.error("Basebinary not readable at path: {0}".format(fw_path))

def _cmd_do_feat_command(client, unused):
	print client.get_features()

def _cmd_decrypt(args):
	(inpath, outpath) = args
	with open(inpath, "rb") as infile:
		indata = infile.read()
	
	#XXX: lazy, fixme
	try:
		outdata = Basebinary.parse(indata)
	except BasebinaryError:
		raise
	else:
		with open(outpath, "wb") as outfile:
			outfile.write(outdata)

def _cmd_extract(args):
	(inpath, outpath) = args
	with open(inpath, "rb") as infile:
		indata = infile.read()
	
	#XXX: lazy, fixme
	try:
		outdata = Basebinary.extract(indata)
	except BasebinaryError:
		raise
	else:
		with open(outpath, "wb") as outfile:
			outfile.write(outdata)

def _cmd_srp_test(client, unused):
	print "SRP testing"
	client.authenticate_AppleSRP()
	client.close()


def main():
	#TODO: add CLI arg for verbosity
	logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
	
	parser = _ArgParser()
	
	parameters_group = parser.add_argument_group("AirPort client parameters")
	parameters_group.add_argument("-t", "--target", metavar="address", help="IP address or hostname of the target router")
	parameters_group.add_argument("-p", "--password", metavar="password", help="router admin password")
	
	airport_client_group = parser.add_argument_group("AirPort client commands")
	airport_client_group.add_argument("--listprop", action="store_const", const=True, help="list supported properties")
	airport_client_group.add_argument("--helpprop", metavar="property", nargs=1, help="print the description of the specified property")
	airport_client_group.add_argument("--getprop", metavar="property", nargs=1, help="get the value of the specified property")
	airport_client_group.add_argument("--setprop", metavar=("property", "value"), nargs=2, help="set the value of the specified property")
	airport_client_group.add_argument("--dumpprop", action="store_const", const=True, help="dump values of all supported properties")
	airport_client_group.add_argument("--acpprop", action="store_const", const=True, help="get acp acpprop list")
	airport_client_group.add_argument("--dump-syslog", action="store_const", const=True, help="dump the router system log")
	airport_client_group.add_argument("--reboot", action="store_const", const=True, help="reboot device")
	airport_client_group.add_argument("--factory-reset", action="store_const", const=True, help="RESET EVERYTHING and reboot; you have been warned!")
	airport_client_group.add_argument("--flash-primary", metavar="firmware_path", nargs=1, help="flash primary partition firmware")
	airport_client_group.add_argument("--do-feat-command", action="store_const", const=True, help="send 0x1b (feat) command")
	
	basebinary_group = parser.add_argument_group("Basebinary commands")
	basebinary_group.add_argument("--decrypt", metavar=("inpath", "outpath"), nargs=2, help="decrypt the basebinary")
	basebinary_group.add_argument("--extract", metavar=("inpath", "outpath"), nargs=2, help="extract the gzimg contents")
	
	test_group = parser.add_argument_group("Test arguments")
	test_group.add_argument("--srp-test", action="store_const", const=True, help="SRP (requires OS X)")
	
	args_dict = vars(parser.parse_args())
	
	#TODO: give each element a dict containing parameter requirements/argparse infos, then generate parser based on this
	commands = {
		"listprop": "local",
		"helpprop": "local",
		"getprop": "remote_admin",
		"setprop": "remote_admin",
		"dumpprop": "remote_admin",
		"acpprop": "remote_admin",
		"dump_syslog": "remote_admin",
		"reboot": "remote_admin",
		"factory_reset": "remote_admin",
		"flash_primary": "remote_admin",
		"do_feat_command": "remote_noauth",
		"decrypt": "local",
		"extract": "local",
		"srp_test": "remote_admin",
		}
	
	target = args_dict["target"]
	password = args_dict["password"]
	command_args = {k: v for k, v in args_dict.items() if k in commands and v is not None}
	
	if len(command_args) == 0:
		logging.error("must specify a command")
		
	elif len(command_args) == 1:
		#TODO: clean this up a bit
		cmd, arg = command_args.popitem()
		assert commands[cmd] in ["local", "remote_noauth", "remote_admin"], "unknown command type \"{0}\"".format(commands[cmd])
		cmd_handler_name = "_cmd_{0}".format(cmd)
		cmd_handler = globals().get(cmd_handler_name, _cmd_not_implemented)
		
		if commands[cmd] == "local":
			cmd_handler(arg)
		
		if commands[cmd] == "remote_noauth":
			if target is not None:
				c = ACPClient(target)
				c.connect()
				cmd_handler(c, arg)
				c.close()
			else:
				logging.error("must specify a target")
		
		if commands[cmd] == "remote_admin":
			if target is not None and password is not None:
				c = ACPClient(target, password)
				c.connect()
				cmd_handler(c, arg)
				c.close()
			else:
				logging.error("must specify a target and administrator password")
				
	else:
		logging.error("multiple commands not supported, choose only one")


================================================
FILE: acp/clibs/AppleSRP.py
================================================
from ctypes import *


#XXX: hax to display NULL pointer thingies
def _fmt_void_ptr(value):
	if value is None:
		return 0
	return value

def _fmt_cstr(value):
	if cast(value, c_void_p).value is None:
		return "<null cstr>"
	return value.contents.get_data_buffer().encode("hex")

def _fmt_ccz_class(value):
	if cast(value, c_void_p).value is None:
		return "<null ccz_class>"
	return value.contents

def _fmt_ccz(value):
	if cast(value, c_void_p).value is None:
		return "<null ccz>"
	return value.contents


class cstr(Structure):
	_fields_ = [("data", c_void_p),
	            ("length", c_long),
	            ("cap", c_long),
	            ("ref", c_int),
	            ("allocator", c_void_p)]
	
	def __str__(self):
		s =  "cstr:  {0!r}\n".format(self)
		s += "data:  {0}\n".format(self.get_data_buffer().encode("hex"))
		s += "len:   {0}\n".format(self.length)
		s += "cap:   {0}\n".format(self.cap)
		s += "ref:   {0}\n".format(self.ref)
		s += "alloc: {0:#x}".format(self.allocator)
		return s
	
	def get_data_buffer(self):
		return string_at(self.data, self.length)


# define SHA_LBLOCK      16
'''
typedef struct SHAstate_st {
    SHA_LONG h0, h1, h2, h3, h4;
    SHA_LONG Nl, Nh;
    SHA_LONG data[SHA_LBLOCK];
    unsigned int num;
} SHA_CTX;
'''
class SHA1_CTX(Structure):
	_fields_ = [("h0", c_uint),
	            ("h1", c_uint),
	            ("h2", c_uint),
	            ("h3", c_uint),
	            ("h4", c_uint),
	            ("Nl", c_uint),
	            ("Nh", c_uint),
	            ("data", c_uint * 16), # uninitialized data???
	            ("num", c_uint)]
	
	def __str__(self):
		s =  "SHA1_CTX: {0!r}\n".format(self)
		s += "h0:       {0:#x}\n".format(self.h0)
		s += "h1:       {0:#x}\n".format(self.h1)
		s += "h2:       {0:#x}\n".format(self.h2)
		s += "h3:       {0:#x}\n".format(self.h3)
		s += "h4:       {0:#x}\n".format(self.h4)
		s += "Nl:       {0}\n".format(self.Nl)
		s += "Nh:       {0}\n".format(self.Nh)
		s += "data:     {0}\n".format("".join(["{0:08x}".format(self.data[i]) for i in range(16)])) # uninitialized data???
		s += "num:      {0}".format(self.num)
		return s


#define RFC2945_KEY_LEN 40	/* length of session key (bytes) */
#define RFC2945_RESP_LEN 20	/* length of proof hashes (bytes) */
'''
struct client_meth_st {
  SHA1_CTX hash;
  SHA1_CTX ckhash;
  unsigned char k[RFC2945_KEY_LEN];
};
'''
class client_meth_st(Structure):
	_fields_ = [("hash", SHA1_CTX),
	            ("ckhash", SHA1_CTX),
	            ("k", c_ubyte * 40)]
	
	def __str__(self):
		s =  "client_meth_st: {0!r}\n".format(self)
		s += "hash:           {0}\n".format(self.hash)
		s += "ckhash:         {0}\n".format(self.ckhash)
		s += "k:              {0}".format(self.k)
		return s


'''
struct server_meth_st {
  SHA1_CTX hash;
  SHA1_CTX ckhash;
  SHA1_CTX oldhash;
  SHA1_CTX oldckhash;
  unsigned char k[RFC2945_KEY_LEN];
  unsigned char r[RFC2945_RESP_LEN];
};
'''
class server_meth_st(Structure):
	_fields_ = [("hash", SHA1_CTX),
	            ("ckhash", SHA1_CTX),
	            ("oldhash", SHA1_CTX),
	            ("oldckhash", SHA1_CTX),
	            ("k", c_ubyte * 40),
	            ("r", c_ubyte * 20)]
	
	def __str__(self):
		s =  "server_meth_st: {0!r}\n".format(self)
		s += "hash:           {0}\n".format(self.hash)
		s += "ckhash:         {0}\n".format(self.ckhash)
		s += "oldhash:        {0}\n".format(self.oldhash)
		s += "oldckhash:      {0}\n".format(self.oldckhash)
		s += "k:              {0}\n".format(self.k)
		s += "r:              {0}".format(self.r)
		return s


'''
struct ccz_class {
	void *ctx;
	void *(*ccz_alloc)(void *, size_t);
	void *(*ccz_realloc)(void *, size_t, void *, size_t);
	void (*ccz_free)(void *, size_t, void *);
};
'''
class ccz_class(Structure):
	_fields_ = [("ctx", c_void_p),
	            ("ccz_alloc", c_void_p),
	            ("ccz_realloc", c_void_p),
	            ("ccz_free", c_void_p)]
	
	def __str__(self):
		s =  "ccz_class:   {0!r}\n".format(self)
		s += "ctx:         {0:#x}\n".format(_fmt_void_ptr(self.ctx))
		s += "ccz_alloc:   {0:#x}\n".format(self.ccz_alloc)
		s += "ccz_realloc: {0:#x}\n".format(self.ccz_realloc)
		s += "ccz_free:    {0:#x}".format(self.ccz_free)
		return s


'''
struct ccz {
    size_t n;
    struct ccz_class *isa;
    int sac;
    cc_unit *u;
};
typedef struct ccz ccz;
'''
class ccz(Structure):
	_fields_ = [("n", c_size_t),
	            ("isa", POINTER(ccz_class)),
	            ("sac", c_int),
	            ("u", c_void_p)]
	
	def __str__(self):
		s =  "ccz: {0!r}\n".format(self)
		s += "n:   {0}\n".format(self.n)
		s += "isa: {0}\n".format(_fmt_ccz_class(self.isa))
		s += "sac: {0}\n".format(self.sac)
		s += "u:   {0:#x}".format(_fmt_void_ptr(self.u))
		return s


'''
struct srp_st {
  int magic;	/* To distinguish client from server (and for sanity) */

  int flags;

  cstr * username;

  BigInteger modulus;
  BigInteger generator;
  cstr * salt;

  BigInteger verifier;
  BigInteger password;

  BigInteger pubkey;
  BigInteger secret;
  BigInteger u;

  BigInteger key;

  cstr * ex_data;

  SRP_METHOD * meth;
  void * meth_data;

  BigIntegerCtx bctx;	     /* to cache temporaries if available */
  BigIntegerModAccel accel;  /* to accelerate modexp if available */

  SRP_CLIENT_PARAM_VERIFY_CB param_cb;	/* to verify params */
  SRP_SERVER_LOOKUP * slu;   /* to look up users */
};
'''
class srp_st(Structure):
	_fields_ = [("magic", c_int),
	            ("flags", c_int),
	            ("username", POINTER(cstr)),
	            ("modulus", POINTER(ccz)),
	            ("generator", POINTER(ccz)),
	            ("salt", POINTER(cstr)),
	            ("verifier", POINTER(ccz)),
	            ("password", POINTER(ccz)),
	            ("pubkey", POINTER(ccz)),
	            ("secret", POINTER(ccz)),
	            ("u", POINTER(ccz)),
	            ("key", POINTER(ccz)),
	            ("ex_data", POINTER(cstr)),
	            ("meth", c_void_p),
	            #XXXXXXXXXXXXXXXX
	            ("meth_data", POINTER(client_meth_st)),
	            #("meth_data", POINTER(server_meth_st)),
	            #XXXXXXXXXXXXXXXX
	            ("bctx", c_void_p),
	            ("accel", c_void_p),
	            ("param_cb", c_void_p),
	            ("slu", c_void_p)]
	
	def __str__(self):
		s =  "*** START ***\n"
		s += "srp_st:    {0!r}\n".format(self)
		s += "magic:     {0}\n".format(self.magic)
		s += "flags:     {0}\n".format(self.flags)
		s += "username:  {0}\n".format(_fmt_cstr(self.username))
		s += "modulus:   {0}\n".format(_fmt_ccz(self.modulus))
		s += "generator: {0}\n".format(_fmt_ccz(self.generator))
		s += "salt:      {0}\n".format(_fmt_cstr(self.salt))
		s += "verifier:  {0}\n".format(_fmt_ccz(self.verifier))
		s += "password:  {0}\n".format(_fmt_ccz(self.password))
		s += "pubkey:    {0}\n".format(_fmt_ccz(self.pubkey))
		s += "secret:    {0}\n".format(_fmt_ccz(self.secret))
		s += "u:         {0}\n".format(_fmt_ccz(self.u))
		s += "key:       {0}\n".format(_fmt_ccz(self.key))
		s += "ex_data:   {0}\n".format(_fmt_cstr(self.ex_data))
		s += "meth:      {0:#x}\n".format(_fmt_void_ptr(self.meth))
		s += "meth_data: {0}\n".format(self.meth_data.contents) #XXXXXXXXXXXXXXXX
		s += "bctx:      {0:#x}\n".format(_fmt_void_ptr(self.bctx))
		s += "accel:     {0:#x}\n".format(_fmt_void_ptr(self.accel))
		s += "param_cb:  {0:#x}\n".format(_fmt_void_ptr(self.param_cb))
		s += "slu:       {0:#x}\n".format(_fmt_void_ptr(self.slu))
		s += "**** END ****"
		return s


__AppleSRP = cdll.LoadLibrary("/System/Library/PrivateFrameworks/AppleSRP.framework/Versions/A/AppleSRP")
#print "AppleSRP:", __AppleSRP

# SRP_METHOD *SRP6a_client_method(void)
SRP6a_client_method = __AppleSRP.SRP6a_client_method
SRP6a_client_method.restype = c_void_p

# SRP_METHOD *SRP6a_server_method(void)
SRP6a_server_method = __AppleSRP.SRP6a_server_method
SRP6a_server_method.restype = c_void_p

# SRP *SRP_new(SRP_METHOD *meth)
SRP_new = __AppleSRP.SRP_new
#SRP_new.restype = c_void_p
SRP_new.restype = POINTER(srp_st)
SRP_new.argtypes = [ c_void_p ]

# SRP_RESULT SRP_set_username(SRP *srp, const char *username)
SRP_set_username = __AppleSRP.SRP_set_username
SRP_set_username.argtypes = [ c_void_p, c_char_p ]

# SRP_RESULT SRP_set_params(SRP *srp, const unsigned char *modulus, int modlen,
#                           const unsigned char *generator, int genlen,
#                           const unsigned char *salt, int saltlen)
SRP_set_params = __AppleSRP.SRP_set_params
SRP_set_params.argtypes = [ c_void_p, c_char_p, c_int, c_char_p, c_int, c_char_p, c_int ]

# SRP_RESULT SRP_gen_pub(SRP *srp, cstr **result)
SRP_gen_pub = __AppleSRP.SRP_gen_pub
SRP_gen_pub.argtypes = [ c_void_p, POINTER(POINTER(cstr)) ]

# SRP_RESULT SRP_set_auth_password(SRP *srp, const unsigned char *password, int passlen)
SRP_set_auth_password = __AppleSRP.SRP_set_auth_password
SRP_set_auth_password.argtypes = [ c_void_p, c_char_p, c_int ]

# SRP_RESULT SRP_compute_key(SRP *srp, cstr **result, const unsigned char *pubkey, int pubkeylen)
SRP_compute_key = __AppleSRP.SRP_compute_key
SRP_compute_key.argtypes = [ c_void_p, POINTER(POINTER(cstr)), c_char_p, c_int ]

# SRP_RESULT SRP_respond(SRP *srp, cstr **proof)
SRP_respond = __AppleSRP.SRP_respond
SRP_respond.argtypes = [ c_void_p, POINTER(POINTER(cstr)) ]

# SRP_RESULT SRP_verify(SRP *srp, const unsigned char *proof, int prooflen)
SRP_verify = __AppleSRP.SRP_verify
SRP_verify.argtypes = [ c_void_p, c_char_p, c_int ]

# SRP_RESULT SRP_free(SRP *srp)
SRP_free = __AppleSRP.SRP_free
SRP_free.argtypes = [ c_void_p ]

# cstr *cstr_new(void)
cstr_new = __AppleSRP.cstr_new
cstr_new.restype = POINTER(cstr)

# void cstr_free(cstr *str)
cstr_free = __AppleSRP.cstr_free
cstr_free.restype = None
cstr_free.argtypes = [ POINTER(cstr) ]


================================================
FILE: acp/clibs/__init__.py
================================================


================================================
FILE: acp/client.py
================================================
import logging
import os
import struct
import time

from .cflbinary import CFLBinaryPListComposer, CFLBinaryPListParser
from .message import ACPMessage
from .property import ACPProperty
from .session import ACPClientSession


class ACPClient(object):
	def __init__(self, target, password=""):
		self.target = target
		self.password = password
		
		self.session = ACPClientSession(target, password)
	
	
	def connect(self):
		self.session.connect()
	
	
	def close(self):
		self.session.close()
	
	
	def send(self, data):
		self.session.send(data)
	
	
	def recv(self, size):
		return self.session.recv(size)
	
	
	def recv_message_header(self):
		return self.recv(ACPMessage.header_size)
	
	
	def recv_property_element_header(self):
		return self.recv(ACPProperty.element_header_size)
	
	
	def get_properties(self, prop_names=[]):
		# request property by sending name and "null" value
		payload = ""
		for name in prop_names:
			payload += ACPProperty.compose_raw_element(0, ACPProperty(name))
		
		request = ACPMessage.compose_getprop_command(4, self.password, payload)
		self.send(request)
		
		raw_reply = self.recv_message_header()
		reply_header = ACPMessage.parse_raw(raw_reply)
		
		if reply_header.error_code != 0:
			print "get_properties error code: {0:#x}".format(reply_header.error_code)
			#XXX: blah, what to do...
			return []
		
		props = []
		while True:
			prop_header = self.recv_property_element_header()
			name, flags, size = ACPProperty.parse_raw_element_header(prop_header)
			logging.debug("name  ".format(name))
			logging.debug("flags ".format(flags))
			logging.debug("size  ".format(size))
			
			prop_data = self.recv(size)
			logging.debug("prop_data {0!r}".format(prop_data))
			
			if flags & 1:
				(error_code, ) = struct.unpack(">I", prop_data)
				print "error requesting value for property \"{0}\": {1:#x}".format(name, error_code)
				continue
			
			prop = ACPProperty(name, prop_data)
			logging.debug("prop {0!r}".format(prop))
			
			#XXX: this is still a bit ugly
			if prop.name is None and prop.value is None:
				logging.debug("found empty prop end marker")
				break
			
			#XXX: should we should return dict(name=name, prop=ACPProperty(name, value)) instead?
			props.append(prop)
			
		return props
	
	
	def set_properties(self, props_dict={}):
		payload = ""
		for name, prop in props_dict.iteritems():
			logging.debug("prop: {0!r}".format(prop))
			payload += ACPProperty.compose_raw_element(0, prop)
		request = ACPMessage.compose_setprop_command(0, self.password, payload)
		self.send(request)
		
		raw_reply = self.recv_message_header()
		reply_header = ACPMessage.parse_raw(raw_reply)
		
		if reply_header.error_code != 0:
			print "set_properties error code: {0:#x}".format(reply_header.error_code)
			#XXX: blah, what to do...
			return
		
		prop_header = self.recv_property_element_header()
		name, flags, size = ACPProperty.parse_raw_element_header(prop_header)
		logging.debug("name  {0!r}".format(name))
		logging.debug("flags {0!r}".format(flags))
		logging.debug("size  {0!r}".format(size))
		
		prop_data = self.recv(size)
		logging.debug("prop_data {0!r}".format(prop_data))
		
		if flags & 1:
			(error_code, ) = struct.unpack(">I", prop_data)
			print "error setting value for property \"{0}\": {1:#x}".format(name, error_code)
			return
			
		prop = ACPProperty(name, prop_data)
		logging.debug("prop {0!r}".format(prop))
		
		#XXX: this is still a bit ugly
		if prop.name is None and prop.value is None:
			logging.debug("found empty prop end marker")
	
	
	def get_features(self):
		self.send(ACPMessage.compose_feat_command(0))
		
		reply_header = ACPMessage.parse_raw(self.recv_message_header())
		
		reply = self.recv(reply_header.body_size)
		
		return CFLBinaryPListParser.parse(reply)
	
	
	def flash_primary(self, payload):
		self.send(ACPMessage.compose_flash_primary_command(0, self.password, payload))
		
		reply_header = ACPMessage.parse_raw(self.recv_message_header())
		
		return self.recv(reply_header.body_size)
	
	
	def authenticate_AppleSRP(self):
		#XXX: STILL TESTING SHIT
		import ctypes
		from .clibs import AppleSRP
		from collections import OrderedDict		
		
		username = u"admin"
		
		dic = OrderedDict([(u"state", 1), (u"username", username)])
		payload = CFLBinaryPListComposer.compose(dic)
		raw_message = ACPMessage.compose_auth_command(4, payload)
		self.send(raw_message)
		
		raw_reply_header = self.recv_message_header()
		reply_header = ACPMessage.parse_raw(raw_reply_header)
		
		if reply_header.error_code != 0:
			logging.error("authenticate error code: {0:#x}".format(reply_header.error_code))
			#XXX: blah, what to do...
			return
		
		logging.debug("recv_size: {0}".format(reply_header.body_size))
		raw_message = self.recv(reply_header.body_size)
		logging.debug("raw_message: {0!r}".format(raw_message))
		params1 = CFLBinaryPListParser.parse(raw_message)
		logging.debug(params1)
		
		n = params1[u"modulus"]
		g = params1[u"generator"]
		salt = params1[u"salt"]
		server_pkey = params1[u"publicKey"]
		
		nhex = n.encode("hex")
		ghex = g.encode("hex")
		
		logging.debug("nhex: {0}".format(nhex))
		logging.debug("ghex: {0}".format(ghex))
		logging.debug("salt: {0}".format(salt.encode("hex")))
		logging.debug("server_pkey: {0}".format(server_pkey.encode("hex")))
		
		# create SRP context
		asrp = AppleSRP.SRP_new(AppleSRP.SRP6a_client_method())
		#logging.debug(asrp.contents)
		
		# set username
		logging.debug("SRP_set_username: {0}".format(AppleSRP.SRP_set_username(asrp, username)))
		#logging.debug(asrp.contents)
		
		# set parameters from server
		logging.debug("SRP_set_params: {0}".format(AppleSRP.SRP_set_params(asrp, n, len(n), g, len(g), salt, len(salt))))
		#logging.debug(asrp.contents)
		
		# generate public key
		client_gen_pubkey_ptr = AppleSRP.cstr_new()
		logging.debug("SRP_gen_pub: {0}".format(AppleSRP.SRP_gen_pub(asrp, ctypes.byref(client_gen_pubkey_ptr))))
		client_gen_pubkey = client_gen_pubkey_ptr.contents
		logging.debug(client_gen_pubkey)
		#logging.debug(asrp.contents)

		# set password
		logging.debug("SRP_set_auth_password: {0}".format(AppleSRP.SRP_set_auth_password(asrp, self.password, len(self.password))))
		#logging.debug(asrp.contents)
		
		# compute key
		client_computed_key_ptr = AppleSRP.cstr_new()
		logging.debug("SRP_compute_key: {0}".format(AppleSRP.SRP_compute_key(asrp, ctypes.byref(client_computed_key_ptr), server_pkey, len(server_pkey))))
		client_computed_key = client_computed_key_ptr.contents
		logging.debug(client_computed_key)
		client_computed_key_buf = client_computed_key.get_data_buffer()
		#logging.debug(asrp.contents)
		
		# generate challenge response
		client_proof_ptr = AppleSRP.cstr_new()
		logging.debug("SRP_respond: {0}".format(AppleSRP.SRP_respond(asrp, ctypes.byref(client_proof_ptr))))
		client_proof = client_proof_ptr.contents
		logging.debug(client_proof)
		#logging.debug(asrp.contents)
		
		client_iv = os.urandom(0x10)
		client_pkey = client_gen_pubkey.get_data_buffer()
		client_proof = client_proof.get_data_buffer()
		
		dic = OrderedDict([(u"iv", client_iv), (u"publicKey", client_pkey), (u"state", 3), (u"response", client_proof)])
		payload = CFLBinaryPListComposer.compose(dic)
		raw_message = ACPMessage.compose_auth_command(4, payload)
		self.send(raw_message)
		
		raw_reply_header = self.recv_message_header()
		reply_header = ACPMessage.parse_raw(raw_reply_header)
		
		if reply_header.error_code != 0:
			logging.debug("authenticate error code: {0:#x}".format(reply_header.error_code))
			#XXX: blah, what to do...
			return
		
		logging.debug("recv_size: {0}".format(reply_header.body_size))
		raw_message = self.recv(reply_header.body_size)
		logging.debug("raw_message: {0!r}".format(raw_message))
		params2 = CFLBinaryPListParser.parse(raw_message)
		logging.debug(params2)
	
		server_proof = params2[u"response"]
		server_iv = params2[u"iv"]
		
		# verify server response
		logging.debug("SRP_verify: {0}".format(AppleSRP.SRP_verify(asrp, server_proof, len(server_proof))))
		#logging.debug(asrp.contents)
		
		# cleanup
		logging.debug("Freeing cstr(s)")
		AppleSRP.cstr_free(client_gen_pubkey_ptr)
		AppleSRP.cstr_free(client_computed_key_ptr)
		AppleSRP.cstr_free(client_proof_ptr)
		logging.debug("SRP_free: {0}".format(AppleSRP.SRP_free(asrp)))
		
		###self.session.enable_encryption(client_computed_key_buf, client_iv, server_iv)



================================================
FILE: acp/encryption.py
================================================
from Crypto.Cipher import AES
from Crypto.Protocol import KDF
from Crypto.Util import Counter


PBKDF_salt0 = "F072FA3F66B410A135FAE8E6D1D43D5F".decode("hex")
PBKDF_salt1 = "BD0682C9FE79325BC73655F4174B996C".decode("hex")


class _ACPEncryptionContext(object):
	def __init__(self, key, iv):
		self.key = key
		self.iv = iv
		
		self.ctr = Counter.new(128, initial_value=int(iv.encode("hex"), 16))
		self.cipher = AES.new(key, AES.MODE_CTR, counter=self.ctr)


class ACPEncryption(object):
	def __init__(self, key, client_iv, server_iv):
		self._client_context = self._init_client_context(key, client_iv)
		self._server_context = self._init_server_context(key, server_iv)
	
	def _init_client_context(cls, key, iv):
		derived_key = KDF.PBKDF2(key, PBKDF_salt0, 16, 5)
		return _ACPEncryptionContext(derived_key, iv)
	
	def _init_server_context(cls, key, iv):
		derived_key = KDF.PBKDF2(key, PBKDF_salt1, 16, 7)
		return _ACPEncryptionContext(derived_key, iv)
	
	
	def client_decrypt(self, data):
		return self._client_context.cipher.decrypt(data)
	
	
	def client_encrypt(self, data):
		return self._client_context.cipher.encrypt(data)
	
	
	def server_decrypt(self, data):
		return self._server_context.cipher.decrypt(data)
	
	
	def server_encrypt(self, data):
		return self._server_context.cipher.encrypt(data)



================================================
FILE: acp/exception.py
================================================
#TODO: put other exceptions in here...

class ACPError(Exception):
	"""Base class for exceptions in this module."""
	pass


class ACPClientError(ACPError):
	"""Exception raised for errors in the ACP client"""
	pass


class ACPCommandLineError(ACPError):
	"""Exception raised for command line invocation errors"""
	pass


class ACPMessageError(ACPError):
	"""Exception raised for errors processing ACP packets"""
	pass


class ACPPropertyError(ACPError):
	"""Exception raised for errors processing ACP properties"""
	pass



================================================
FILE: acp/keystream.py
================================================
"""Static key/seed for keystream generation"""
ACP_STATIC_KEY = "5b6faf5d9d5b0e1351f2da1de7e8d673".decode("hex")

def generate_acp_keystream(length):
	"""Get key used to encrypt the header key (and some message data?)
	
	Args:
		length (int): length of keystream to generate
	
	Returns:
		String of requested length
	
	Note:
		Keystream repeats every 256 bytes
	
	"""
	key = ""
	key_idx = 0
	
	while (key_idx < length):
		key += chr((key_idx + 0x55 & 0xFF) ^ ord(ACP_STATIC_KEY[key_idx % len(ACP_STATIC_KEY)]))
		key_idx += 1
	
	return key


================================================
FILE: acp/message.py
================================================
import logging
import struct
import zlib

from .exception import ACPMessageError
from .keystream import *


def _generate_acp_header_key(password):
	"""
	Encrypt password for ACP message header key field
	
	Note:
		Truncates the password at 0x20 bytes, not sure if this is the right thing to use in all cases
	
	Args:
		password (str): system password of the router (syAP)
	
	Returns:
		String containing encrypted password of proper length for the header field
	
	"""
	pw_len = 0x20
	pw_key = generate_acp_keystream(pw_len)
	
	# pad with NULLs
	pw_buf = password[:pw_len].ljust(pw_len, "\x00")
	enc_pw_buf = ""
	for i in range(pw_len):
		enc_pw_buf += chr(ord(pw_key[i]) ^ ord(pw_buf[i]))	
	
	return enc_pw_buf


class ACPMessage(object):
	"""ACP message composition and parsing"""
	
	#XXX: struct is stupid about unpacking unsigned ints > 0x7fffffff, so treat everything as signed and
	#     "cast" where necessary. Should we switch to using ctypes?
	_header_format = struct.Struct("!4s8i12x32s48x")
	_header_magic  = "acpp"
	
	header_size = _header_format.size
	
	
	def __init__(self, version, flags, unused, command, error_code, key, body=None, body_size=None):
		self.version = version
		self.flags = flags
		self.unused = unused
		self.command = command
		self.error_code = error_code
		
		# body is not specified, this is a stream header
		if body == None:
			# the body size is already specified, don't override it
			self.body_size = body_size if body_size != None else -1
			self.body_checksum = 1 # equivalent to zlib.adler32("")
		else:
			# the body size is already specified, don't override it
			self.body_size = body_size if body_size != None else len(body)
			self.body_checksum = zlib.adler32(body)
		
		self.key = key
		self.body = body
	
	
	def __str__(self):
		s =  "ACPMessage:    {0!r}\n".format(self)
		s += "body_checksum: {0:#x}\n".format(self.body_checksum)
		s += "body_size:     {0:#x}\n".format(self.body_size)
		s += "flags:         {0:#x}\n".format(self.flags)
		s += "unused:        {0:#x}\n".format(self.unused)
		s += "command:       {0:#x}\n".format(self.command)
		s += "error_code:    {0:#x}\n".format(self.error_code)
		s += "key:           {0!r}".format(self.key)
		return s
	
	
	@classmethod
	def parse_raw(cls, data):
		# bail early if there is not enough data
		if len(data) < cls.header_size:
			raise ACPMessageError("need to pass at least {0} bytes".format(cls.header_size))
		header_data = data[:cls.header_size]
		# make sure there's data beyond the header before we try to access it
		body_data = data[cls.header_size:] if len(data) > cls.header_size else None
		
		(magic, version, header_checksum, body_checksum, body_size, flags, unused, command, error_code, key) = cls._header_format.unpack(header_data)
		logging.debug("ACP message header fields, parsed not validated")
		logging.debug("magic           {0!r}".format(magic))
		logging.debug("header_checksum {0:#x}".format(header_checksum))
		logging.debug("body_checksum   {0:#x}".format(body_checksum))
		logging.debug("body_size       {0:#x}".format(body_size))
		logging.debug("flags           {0:#x}".format(flags))
		logging.debug("unused          {0:#x}".format(unused))
		logging.debug("command         {0:#x}".format(command))
		logging.debug("error_code      {0:#x}".format(error_code))
		logging.debug("key             {0!r}".format(key))
		
		if magic != cls._header_magic:
			raise ACPMessageError("bad header magic")
		
		if version not in [0x00000001, 0x00030001]:
			raise ACPMessageError("invalid version")
		
		#TODO: can we zero the header_checksum field without recreating the struct (how?)
		tmphdr = cls._header_format.pack(magic, version, 0, body_checksum, body_size, flags, unused, command, error_code, key)
		if header_checksum != zlib.adler32(tmphdr):
			raise ACPMessageError("header checksum does not match")
		
		if body_data and body_size == -1:
			raise ACPMessageError("cannot handle stream header with data attached")
		
		if body_data and body_size != len(body_data):
			raise ACPMessageError("message body size does not match available data")
		
		if body_data and body_checksum != zlib.adler32(body_data):
			raise ACPMessageError("body checksum does not match")
		
		#TODO: check flags
		
		#TODO: check status
		
		if command not in [1, 3, 4, 5, 6, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b]:
			raise ACPMessageError("unknown command")
		
		#TODO: check error code
		
		return cls(version, flags, unused, command, error_code, key, body_data, body_size)
	
	
	@classmethod
	def compose_echo_command(cls, flags, password, payload):
		return cls(0x00030001, flags, 0, 1, 0, _generate_acp_header_key(password), payload)._compose_raw_packet()
	
	
	@classmethod
	def compose_flash_primary_command(cls, flags, password, payload):
		return cls(0x00030001, flags, 0, 3, 0, _generate_acp_header_key(password), payload)._compose_raw_packet()
	
	
	@classmethod
	def compose_flash_secondary_command(cls, flags, password, payload):
		return cls(0x00030001, flags, 0, 5, 0, _generate_acp_header_key(password), payload)._compose_raw_packet()
	
	
	@classmethod
	def compose_flash_bootloader_command(cls, flags, password, payload):
		return cls(0x00030001, flags, 0, 6, 0, _generate_acp_header_key(password), payload)._compose_raw_packet()
	
	
	@classmethod
	def compose_getprop_command(cls, flags, password, payload):
		return cls(0x00030001, flags, 0, 0x14, 0, _generate_acp_header_key(password), payload)._compose_raw_packet()
	
	
	@classmethod
	def compose_setprop_command(cls, flags, password, payload):
		return cls(0x00030001, flags, 0, 0x15, 0, _generate_acp_header_key(password), payload)._compose_raw_packet()
	
	
	@classmethod
	def compose_perform_command(cls, flags, password, payload):
		return cls(0x00030001, flags, 0, 0x16, 0, _generate_acp_header_key(password), payload)._compose_raw_packet()
	
	
	@classmethod
	def compose_monitor_command(cls, flags, password, payload):
		return cls(0x00030001, flags, 0, 0x18, 0, _generate_acp_header_key(password), payload)._compose_raw_packet()
	
	
	@classmethod
	def compose_rpc_command(cls, flags, password, payload):
		return cls(0x00030001, flags, 0, 0x19, 0, _generate_acp_header_key(password), payload)._compose_raw_packet()
	
	
	@classmethod
	def compose_auth_command(cls, flags, payload):
		return cls(0x00030001, flags, 0, 0x1a, 0, _generate_acp_header_key(""), payload)._compose_raw_packet()
	
	
	@classmethod
	def compose_feat_command(cls, flags):
		return cls(0x00030001, flags, 0, 0x1b, 0, _generate_acp_header_key(""))._compose_raw_packet()
	
	
	@classmethod
	def compose_message_ex(cls, version, flags, unused, command, error_code, password, payload, payload_size):
		return cls(version, flags, unused, command, error_code, _generate_acp_header_key(password), payload, payload_size)._compose_raw_packet()
	
	
	def _compose_raw_packet(self):
		"""Compose a request from the client to ACP daemon
		
		Returns:
			String containing message to send
		
		"""
		reply = self._compose_header()
		if self.body:
			reply += self.body
		
		return reply
	
	
	def _compose_header(self):
		"""Compose the message header
		
		Returns:
			String containing header data
		
		"""
		tmphdr = self._header_format.pack(self._header_magic,
		                                  self.version,
		                                  0,
		                                  self.body_checksum,
		                                  self.body_size,
		                                  self.flags,
		                                  self.unused,
		                                  self.command,
		                                  self.error_code,
		                                  self.key)
		
		header = self._header_format.pack(self._header_magic,
		                                  self.version,
		                                  zlib.adler32(tmphdr),
		                                  self.body_checksum,
		                                  self.body_size,
		                                  self.flags,
		                                  self.unused,
		                                  self.command,
		                                  self.error_code,
		                                  self.key)
		
		return header


================================================
FILE: acp/misc.py
================================================
#XXX: this file exists until I think of a better way to do this

def cast_u32(value):
	#XXX: lazy, how do we do this correctly?
	if value < -0x80000000 or value > 0x7FFFFFFF:
		raise Exception("value outside u32 range")
	return value & 0xFFFFFFFF


================================================
FILE: acp/property.py
================================================
import logging
import pprint
import struct

from .cflbinary import CFLBinaryPListParser
from .exception import ACPPropertyError


_acp_properties = [
	# Uncomment and fill out relevant fields to add support for a property
	# Properties must be in the following format:
	# (name, type, description, validation), where
	# name (required) is a 4 character string,
	# type (required) is a valid property type (str, dec, hex, log, mac, cfb, bin)
	# description (required) is a short, one-line description of the property
	# validation (optional) is eval()d to verify the input value for setting a property
	("buil","str","Build string?",""),
	("DynS","cfb","DNS",""),
	#("cfpf","","",""),
	#("cloC","","",""),
	#("cloD","","",""),
	#("conf","","",""),
	("fire","cfb","Firewall???",""),
	#("prob","","",""),
	("srcv","str","Source Version",""),
	("syNm","str","Device name",""),
	#("syDN","","",""),
	#("syPI","","",""),
	("syPW","str","Router administration password",""),
	("syPR","str","syPR string???",""),
	("syGP","str","Router guest password???",""),
	#("syCt","","",""),
	#("syLo","","",""),
	("syDs","str","System description",""),
	("syVs","str","System version",""),
	("syVr","str","System version???",""),
	("syIn","str","System information???",""),
	("syFl","hex","????",""),
	("syAM","str","Model Identifier",""),
	("syAP","dec","Product ID",""),
	("sySN","str","Apple Serial Number",""),
	#("ssSN","","",""),
	#("sySK","","",""),
	("ssSK","str","Apple SKU",""),
	#("syRe","","",""),
	("syLR","cfb","syLR Blob",""),
	("syAR","cfb","syAR Blob",""),
	("syUT","dec","System Uptime",""),
	#("minV","","",""),
	("minS","str","apple-minver",""),
	("chip","str","SoC Description",""),
	#("card","","",""),
	#("memF","","",""),
	#("pool","","",""),
	#("tmpC","","",""),
	#("RPMs","","",""),
	("sySI","cfb","System Info Blob?",""),
	#("fDCY","","",""),
	("TMEn","hex","TMEn???",""),
	("CLTM","cfb","CLTM???",""),
	#("sPLL","","",""),
	#("syTL","","",""),
	#("syST","","",""),
	("sySt","cfb","System Status",""),
	#("syIg","","",""),
	("syBL","str","Bootloader vesrsion string",""),
	("time","dec","System time",""),
	("timz","cfb","Timezone Config Blob",""),
	("usrd","cfb","usrd???",""),
	#("uuid","","",""),
	#("drTY","","",""),
	#("sttE","","",""),
	#("sttF","","",""),
	#("stat","","",""),
	#("sRnd","","",""),
	#("Accl","","",""),
	("dSpn","cfb","Disk spin status?",""),
	("syMS","str","MLB Serial Number",""),
	#("IGMP","","",""),
	("diag","bin","diag???",""),
	#("paFR","","",""),
	("raNm","str","Radio Name",""),
	#("raCl","","",""),
	#("raSk","","",""),
	#("raWM","","",""),
	#("raEA","","",""),
	#("raWE","","",""),
	#("raCr","","",""),
	#("raKT","","",""),
	#("raNN","","",""),
	#("raGK","","",""),
	#("raHW","","",""),
	#("raCM","","",""),
	#("raRo","","",""),
	#("raCA","","",""),
	#("raCh","","",""),
	#("rCh2","","",""),
	#("raWC","","",""),
	#("raDe","","",""),
	#("raMu","","",""),
	#("raLC","","",""),
	#("raLF","","",""),
	#("ra1C","","",""),
	#("raVs","","",""),
	("raMA","mac","Radio MAC Address",""),
	#("raM2","","",""),
	#("raMO","","",""),
	#("raLO","","",""),
	#("raDS","","",""),
	#("raNA","","",""),
	#("raWB","","",""),
	#("raIS","","",""),
	#("raMd","","",""),
	#("raPo","","",""),
	#("raPx","","",""),
	#("raTr","","",""),
	#("raDt","","",""),
	#("raFC","","",""),
	#("raEC","","",""),
	#("raMX","","",""),
	#("raIE","","",""),
	#("raII","","",""),
	#("raB0","","",""),
	#("raB1","","",""),
	#("raB2","","",""),
	#("raSt","","",""),
	#("APSR","","",""),
	#("raTX","","",""),
	#("raRX","","",""),
	#("raAC","","",""),
	("raSL","cfb","Radios list?",""),
	#("raMI","","",""),
	#("raST","","",""),
	#("raDy","","",""),
	#("raEV","","",""),
	#("rTSN","","",""),
	("raSR","cfb","Radio scan results?",""),
	#("eaRA","","",""),
	("WiFi","cfb","Wifi configuration?",""),
	("rCAL","cfb","Radio calibration data?",""),
	#("moPN","","",""),
	#("moAP","","",""),
	#("moUN","","",""),
	#("moPW","","",""),
	#("moIS","","",""),
	#("moLS","","",""),
	#("moLI","","",""),
	#("moID","","",""),
	#("moDT","","",""),
	#("moPD","","",""),
	#("moAD","","",""),
	#("moCC","","",""),
	#("moCR","","",""),
	#("moCI","","",""),
	#("^moM","","",""),
	#("moVs","","",""),
	#("moMP","","",""),
	#("moMF","","",""),
	#("moFV","","",""),
	#("pdFl","","",""),
	#("pdUN","","",""),
	#("pdPW","","",""),
	#("pdAR","","",""),
	#("pdID","","",""),
	#("pdMC","","",""),
	#("peSN","","",""),
	#("peUN","","",""),
	#("pePW","","",""),
	#("peSC","","",""),
	#("peAC","","",""),
	#("peID","","",""),
	#("peAO","","",""),
	("waCV","bin","WAN Config Mode?",""),
	("waIn","bin","WAN Interface Mode?",""),
	#("waD1","","",""),
	#("waD2","","",""),
	#("waD3","","",""),
	#("waC1","","",""),
	#("waC2","","",""),
	#("waC3","","",""),
	("waIP","bin","WAN IP",""),
	#("waSM","","",""),
	("waRA","bin","WAN Upstream Gateway IP",""),
	#("waDC","","",""),
	#("waDS","","",""),
	("waMA","mac","WAN MAC Address",""),
	#("waMO","","",""),
	#("waDN","","",""),
	#("waCD","","",""),
	#("waIS","","",""),
	#("waNM","","",""),
	#("waSD","","",""),
	#("waFF","","",""),
	#("waRO","","",""),
	#("waW1","","",""),
	#("waW2","","",""),
	#("waW3","","",""),
	#("waLL","","",""),
	#("waUB","","",""),
	("waDI","cfb","WAN DHCP Info?",""),
	#("laCV","","",""),
	#("laIP","","",""),
	#("laSM","","",""),
	#("laRA","","",""),
	#("laDC","","",""),
	#("laDS","","",""),
	#("laNA","","",""),
	("laMA","mac","LAN MAC Address",""),
	#("laIS","","",""),
	#("laSD","","",""),
	#("laIA","","",""),
	#("gn6?","","",""),
	#("gn6A","","",""),
	#("gn6P","","",""),
	#("dhFl","","",""),
	#("dhBg","","",""),
	#("dhEn","","",""),
	#("dhSN","","",""),
	#("dhRo","","",""),
	#("dhLe","","",""),
	#("dhMg","","",""),
	#("dh95","","",""),
	("DRes","cfb","DHCP Reservations",""),
	#("dhWA","","",""),
	#("dhDS","","",""),
	#("dhDB","","",""),
	#("dhDE","","",""),
	#("dhDL","","",""),
	("dhSL","cfb","DHCP Server leases?",""),
	#("gnFl","","",""),
	#("gnBg","","",""),
	#("gnEn","","",""),
	#("gnSN","","",""),
	#("gnRo","","",""),
	#("gnLe","","",""),
	#("gnMg","","",""),
	#("gn95","","",""),
	#("gnDi","","",""),
	#("naFl","","",""),
	#("naBg","","",""),
	#("naEn","","",""),
	#("naSN","","",""),
	#("naRo","","",""),
	#("naAF","","",""),
	#("nDMZ","","",""),
	#("pmPI","","",""),
	#("pmPS","","",""),
	#("pmPR","","",""),
	#("pmTa","","",""),
	#("acEn","","",""),
	#("acTa","","",""),
	("tACL","cfb","Timed Access Control",""),
	#("wdFl","","",""),
	#("wdLs","","",""),
	#("dWDS","","",""),
	#("cWDS","","",""),
	#("dwFl","","",""),
	#("raFl","","",""),
	#("raI1","","",""),
	#("raTm","","",""),
	#("raAu","","",""),
	#("raAc","","",""),
	#("raSe","","",""),
	#("raRe","","",""),
	#("raF2","","",""),
	#("raI2","","",""),
	#("raT2","","",""),
	#("raU2","","",""),
	#("raC2","","",""),
	#("raS2","","",""),
	#("raR2","","",""),
	#("raCi","","",""),
	("ntSV","str","NTP Server Hostname",""),
	#("ntpC","","",""),
	#("smtp","","",""),
	#("slog","","",""),
	#("slgC","","",""),
	#("slCl","","",""),
	("slvl","dec","System log severity level?",""),
	#("slfl","","",""),
	("logm","log","System log data",""),
	#("snAF","","",""),
	#("snLW","","",""),
	#("snLL","","",""),
	#("snRW","","",""),
	#("snWW","","",""),
	#("snRL","","",""),
	#("snWL","","",""),
	#("snCS","","",""),
	#("srtA","","",""),
	#("srtF","","",""),
	#("upsF","","",""),
	#("usbF","","",""),
	("USBi","cfb","USB Info",""),
	#("USBL","","",""),
	#("USBR","","",""),
	#("USBO","","",""),
	#("USBs","","",""),
	#("USBo","","",""),
	#("USBh","","",""),
	#("USBb","","",""),
	#("USBn","","",""),
	("prni","cfb","Printer Info?",""),
	#("prnM","","",""),
	#("prnI","","",""),
	#("prnR","","",""),
	#("RUdv","","",""),
	#("RUfl","","",""),
	("MaSt","cfb","USB Mass Storage Info",""),
	#("SMBw","","",""),
	#("SMBs","","",""),
	#("fssp","","",""),
	#("diSD","","",""),
	#("diCS","","",""),
	#("deSt","","",""),
	#("daSt","","",""),
	#("dmSt","","",""),
	#("adNm","","",""),
	#("adBD","","",""),
	#("adAD","","",""),
	#("adHU","","",""),
	#("IDNm","","",""),
	("seFl","bin","????",""), #????
	#("nvVs","","",""),
	#("dbRC","","",""),
	("dbug","hex","Debug flags","0 <= value <= 0xFFFFFFFF"),
	#("dlvl","","",""),
	#("dcmd","","",""),
	#("dsps","","",""),
	#("logC","","",""),
	#("cver","","",""),
	("ctim","hex","ctim???",""),
	#("svMd","","",""),
	#("serM","","",""),
	#("serT","","",""),
	#("emNo","","",""),
	#("effF","","",""),
	#("LLnk","","",""),
	#("WLnk","","",""),
	#("PHYS","","",""),
	#("PHYN","","",""),
	#("Rnfo","","",""),
	#("evtL","","",""),
	#("isAC","","",""),
	#("Adet","","",""),
	("Prof","cfb","Restore Profile Blob",""),
	#("maAl","","",""),
	#("maPr","","",""),
	#("leAc","","",""),
	#("APID","","",""),
	#("AAU ","","",""),
	("lcVs","str","lcVs Version String?",""),
	#("lcVr","","",""),
	#("lcmV","","",""),
	#("lcMV","","",""),
	#("iMTU","","",""),
	("wsci","cfb","wsci Blob",""),
	#("FlSu","","",""),
	("OTPR","hex","machdep.otpval",""),
	("acRB","dec","Reboot device flag","value == 0"),
	("acRI","dec","Reload services??","value == 0"),
	#("acPC","","",""),
	#("acDD","","",""),
	#("acPD","","",""),
	#("acPG","","",""),
	#("acDS","","",""),
	#("acFN","","",""),
	#("acRP","","",""),
	("acRN","dec","Resets something... (?)","value == 0"),
	("acRF","dec","Reset to factory defaults","value == 0"),
	#("MdmH","","",""),
	#("dirf","","",""),
	#("Afrc","","",""),
	#("lebl","","",""),
	#("lebs","","",""),
	("LEDc","dec","LED color/pattern","0 <= value <= 3"),
	#("acEf","","",""),
	#("invr","","",""),
	#("FLSH","","",""),
	#("acPL","","",""),
	#("rReg","","",""),
	#("dReg","","",""),
	("GPIs","bin","GPIOs values","len(value) == 8"),
	#("play","","",""),
	#("paus","","",""),
	#("ffwd","","",""),
	#("rwnd","","",""),
	#("itun","","",""),
	#("plls","","",""),
	#("User","","",""),
	#("Pass","","",""),
	#("itIP","","",""),
	#("itpt","","",""),
	#("daap","","",""),
	#("song","","",""),
	#("arti","","",""),
	#("albm","","",""),
	#("volm","","",""),
	#("rvol","","",""),
	#("Tcnt","","",""),
	#("Bcnt","","",""),
	#("shfl","","",""),
	#("rept","","",""),
	#("auPr","","",""),
	#("auJD","","",""),
	#("auNN","","",""),
	#("auNP","","",""),
	#("aFrq","","",""),
	#("aChn","","",""),
	#("aLvl","","",""),
	#("aPat","","",""),
	#("aSta","","",""),
	#("aStp","","",""),
	#("auCC","","",""),
	#("acmp","","",""),
	#("aenc","","",""),
	#("anBf","","",""),
	#("aWan","","",""),
	#("auRR","","",""),
	#("auMt","","",""),
	#("aDCP","","",""),
	#("DCPc","","",""),
	#("DACP","","",""),
	#("DCPi","","",""),
	#("auSl","","",""),
	#("auFl","","",""),
	("fe01","hex","????",""),
	("feat","str","Supported features?",""),
	("prop","str","Valid acp properties",""),
	("hw01","hex","????",""),
	#("fltr","","",""),
	#("wdel","","",""),
	#("plEB","","",""),
	#("rWSC","","",""),
	#("uDFS","","",""),
	#("dWPA","","",""),
	#("dpFF","","",""),
	#("duLF","","",""),
	#("ieHT","","",""),
	#("dwlX","","",""),
	#("dd11","","",""),
	#("dRdr","","",""),
	#("dotD","","",""),
	#("dotH","","",""),
	#("dPwr","","",""),
	#("wlBR","","",""),
	#("iTIM","","",""),
	#("idAG","","",""),
	#("mvFL","","",""),
	#("mvFM","","",""),
	#("dPPP","","",""),
	#("!mta","","",""),
	#("minR","","",""),
	#("SpTr","","",""),
	#("dRBT","","",""),
	#("dRIR","","",""),
	("pECC","cfb","PCIe ECC Blob?",""),
	#("fxEB","","",""),
	#("fxID","","",""),
	#("fuup","","",""),
	#("fust","","",""),
	#("fuca","","",""),
	("fugp","str","Firmware upgrade progress",""),
	("cks0","hex","Bootloader Flash Checksum",""),
	("cks1","hex","Primary Flash Checksum",""),
	("cks2","hex","Secondary Flash Checksum",""),
	#("ddBg","","",""),
	#("ddEn","","",""),
	#("ddIn","","",""),
	#("ddSm","","",""),
	#("ddEC","","",""),
	#("ddFE","","",""),
	#("ddSR","","",""),
	#("6cfg","","",""),
	#("6aut","","",""),
	#("6Qpd","","",""),
	#("6Wad","","",""),
	#("6Wfx","","",""),
	#("6Wgw","","",""),
	#("6Wte","","",""),
	#("6Lfw","","",""),
	#("6Lad","","",""),
	#("6Lfx","","",""),
	#("6sfw","","",""),
	#("6pmp","","",""),
	#("6trd","","",""),
	#("6sec","","",""),
	#("6fwl","","",""),
	#("6NS1","","",""),
	#("6NS2","","",""),
	#("6NS3","","",""),
	#("6ahr","","",""),
	#("6dhs","","",""),
	#("6dso","","",""),
	#("6PDa","","",""),
	#("6PDl","","",""),
	#("6vlt","","",""),
	#("6plt","","",""),
	#("6CWa","","",""),
	#("6CWp","","",""),
	#("6CWg","","",""),
	#("6CLa","","",""),
	#("6NSa","","",""),
	#("6NSb","","",""),
	#("6NSc","","",""),
	#("6CPa","","",""),
	#("6CPl","","",""),
	#("6!at","","",""),
	("rteI","cfb","rteI Blob",""),
	#("PCLI","","",""),
	#("dxEM","","",""),
	#("dxID","","",""),
	#("dxAI","","",""),
	#("dxIP","","",""),
	#("dxOA","","",""),
	#("dxIA","","",""),
	#("dxC1","","",""),
	#("dxP1","","",""),
	#("dxC2","","",""),
	#("dxP2","","",""),
	#("bjFl","","",""),
	#("bjSd","","",""),
	#("bjSM","","",""),
	#("wbEn","","",""),
	#("wbHN","","",""),
	#("wbHU","","",""),
	#("wbHP","","",""),
	#("wbRD","","",""),
	#("wbRU","","",""),
	#("wbRP","","",""),
	#("wbAC","","",""),
	#("dMac","","",""),
	#("iCld","","",""),
	#("iCLH","","",""),
	#("iCLB","","",""),
	#("SUEn","","",""),
	#("SUAI","","",""),
	#("SUFq","","",""),
	#("SUSv","","",""),
	#("suPR","","",""),
	#("msEn","","",""),
	#("trCo","","",""),
	#("EZCF","","",""),
	#("ezcf","","",""),
	#("gVID","","",""),
	#("wcfg","","",""),
	#("awce","","",""),
	#("wcgu","","",""),
	#("wcgs","","",""),
	#("awcc","","",""),
	]

def _generate_acp_property_dict():	
	props = {}
	for (name, type, description, validation) in _acp_properties:
		# basic validation of tuples
		assert len(name) == 4, "bad name in _acp_properties list: {0}".format(name)
		assert type in ["str", "dec", "hex", "log", "mac", "cfb", "bin"], "bad type in _acp_properties list for name: {0}".format(name)
		assert description, "missing description in _acp_properties list for name: {0}".format(name)
		props[name] = dict(type=type, description=description, validation=validation)
	return props


class ACPPropertyInitValueError(ACPPropertyError):
	pass


class ACPProperty(object):
	_acpprop = _generate_acp_property_dict()
	
	_element_header_format = struct.Struct("!4s2I")
	element_header_size = _element_header_format.size
	
	
	def __init__(self, name=None, value=None):
		# handle "null" property packed name and value first
		if name == "\x00\x00\x00\x00" and value == "\x00\x00\x00\x00":
			name = None
			value = None
		
		if name and name not in self.get_supported_property_names():
			raise ACPPropertyError("invalid property name passed to initializer: {0}".format(name))
		
		if value is not None:
			# accept value as packed binary string or Python type
			prop_type = self.get_property_info_string(name, "type")
			_init_handler_name = "_init_{0}".format(prop_type)
			assert hasattr(self, _init_handler_name), "missing init handler for \"{0}\" property type".format(prop_type)
			_init_handler = getattr(self, _init_handler_name)
			
			logging.debug("old value: {0!r} type: {1}".format(value, type(value)))
			try:
				value = _init_handler(value)
			except ACPPropertyInitValueError as e:
				raise ACPPropertyError("{0!s} provided for \"{1}\" property type: {2!r}".format(e, prop_type, value))
			logging.debug("new value: {0!r} type: {1}".format(value, type(value)))
			
			#XXX: this is still really hacky, should probably do something with anonymous functions or introspection
			validation_expr = self.get_property_info_string(name, "validation")
			if validation_expr and not eval(validation_expr):
				raise ACPPropertyError("invalid value passed to initializer for property \"{0}\": {1}".format(name, repr(value)))
		
		self.name = name
		self.value = value
	
	def _init_dec(self, value):
		if   type(value) == int:
			return value
		elif type(value) == str:
			try:
				return struct.unpack("!I", value)[0]
			except:
				raise ACPPropertyInitValueError("invalid packed binary string")
		else:
			raise ACPPropertyInitValueError("invalid built-in type")
	
	def _init_hex(self, value):
		if   type(value) == int:
			return value
		elif type(value) == str:
			try:
				return struct.unpack("!I", value)[0]
			except:
				raise ACPPropertyInitValueError("invalid packed binary string")
		else:
			raise ACPPropertyInitValueError("invalid built-in type")
	
	def _init_mac(self, value):
		if type(value) == str:
			# first, try as packed binary value
			if len(value) == 6:
				return value
			# second, attempt to unpack colon delimited value
			mac_bytes = value.split(":")
			if len(mac_bytes) == 6:
				try:
					return "".join(mac_bytes).decode("hex")
				except TypeError:
					raise ACPPropertyInitValueError("non-hex digit in value")
			# fallthrough
			raise ACPPropertyInitValueError("invalid value")
		else:
			raise ACPPropertyInitValueError("invalid built-in type")
	
	def _init_bin(self, value):
		if type(value) == str:
			return value
		else:
			raise ACPPropertyInitValueError("invalid built-in type")
	
	def _init_cfb(self, value):
		if type(value) == str:
			return value
		else:
			raise ACPPropertyInitValueError("invalid built-in type")
	
	def _init_log(self, value):
		if type(value) == str:
			return value
		else:
			raise ACPPropertyInitValueError("invalid built-in type")
	
	def _init_str(self, value):
		if type(value) == str:
			return value
		else:
			raise ACPPropertyInitValueError("invalid built-in type")
	
	
	def __repr__(self):
		#XXX: return tuple or dict?
		return repr((self.name, self.value))
	
	
	#TODO: make this function return something other than the formatted value of the property...I keep getting confused by its current shittiness
	def __str__(self):
		#XXX: is this the correct thing to do?
		if self.name is None or self.value is None:
			return ""
		
		prop_type = self.get_property_info_string(self.name, "type")
		_format_handler_name = "_format_{0}".format(prop_type)
		assert hasattr(self, _format_handler_name), "missing format handler for \"{0}\" property type".format(prop_type)
		return getattr(self, _format_handler_name)(self.value)
	
	def _format_dec(self, value):
		return str(value)
	
	def _format_hex(self, value):
		return hex(value)
	
	def _format_mac(self, value):
		mac_bytes = []
		for i in range(6):
			mac_bytes.append(value[i].encode("hex"))
		return "{0}:{1}:{2}:{3}:{4}:{5}".format(*mac_bytes)
	
	def _format_bin(self, value):
		return value.encode("hex")
	
	def _format_cfb(self, value):
		return pprint.pformat(CFLBinaryPListParser.parse(value))
	
	def _format_log(self, value):
		s = ""
		for line in value.strip("\x00").split("\x00"):
			s += "{0}\n".format(line)
		return s
	
	def _format_str(self, value):
		return value
	
	
	@classmethod
	def get_supported_property_names(cls):
		props = []
		for name in cls._acpprop:
			props.append(name)
		return props
	
	
	@classmethod
	def get_property_info_string(cls, prop_name, key):
		#XXX: should we do this differently?
		if prop_name is None:
			return None
		if prop_name not in cls._acpprop:
			logging.error("property \"{0}\" not supported".format(prop_name))
			return None
		prop_info = cls._acpprop[prop_name]
		if key not in prop_info:
			logging.error("invalid property info key \"{0}\"".format(key))
			return None
		return prop_info[key]
	
	
	@classmethod
	def parse_raw_element(cls, data):
		name, flags, size = cls.parse_raw_element_header(data[:cls.element_header_size])
		#TODO: handle flags!???
		return cls(name, data[cls.element_header_size:])
	
	
	@classmethod
	def parse_raw_element_header(cls, data):
		try:
			return cls._element_header_format.unpack(data)
		except struct.error:
			raise ACPPropertyError("failed to parse property element header")
	
	
	@classmethod
	def compose_raw_element(cls, flags, property):
		#TODO: handle flags!???
		#XXX: handles "null" name or value first, but this is currently garbage
		name = property.name if property.name is not None else "\x00\x00\x00\x00"
		value = property.value if property.value is not None else "\x00\x00\x00\x00"
		if   type(value) == int:
			st = struct.Struct(">I")
			#XXX: this could throw an exception, we need to range check int/hex values to ensure they pack into 32 bits still
			return cls.compose_raw_element_header(name, flags, st.size) + st.pack(value)
		elif type(value) == str:
			return cls.compose_raw_element_header(name, flags, len(value)) + value
		else:
			raise ACPPropertyError("unhandled property type for raw element composition")
	
	
	@classmethod
	def compose_raw_element_header(cls, name, flags, size):
		try:
			return cls._element_header_format.pack(name, flags, size)
		except struct.error:
			raise ACPPropertyError("failed to compose property header")


================================================
FILE: acp/session.py
================================================
import logging
import socket

from .encryption import ACPEncryption


ACP_SERVER_PORT = 5009

class _ACPSession(object):
	def __init__(self, target, password):
		#XXX: how should we make this abstract enough to cover client and server?
		self.target = target
		self.password = password
		
		self.sock = None
		
		#self.encryption_context = None
		self.encrypt_method = None
		self.decrypt_method = None
		
		#XXX: AppleSRP hax
		#self.SRP = None
		#self.state = 0
	
	
	def connect(self, port=ACP_SERVER_PORT):
		self.port = port
		self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		logging.info("connecting to host {0}:{1}".format(self.target, self.port))
		self.sock.connect((self.target, self.port))
	
	
	def close(self):
		if self.sock:
			self.sock.close()
			self.sock = None
	
	
	def send(self, data):
		if self.encrypt_method:
			data = self.encrypt_method(data)
		
		if self.sock:
			self.sock.sendall(data)
		#TODO: else? what if sock is not None but not valid in some other way?
	
	
	def _recv_size(self, size):
		recvd_chunks = []
		recvd_size = 0
		while True:
			if recvd_size == size:
				break
			#XXX: this is broken for server receiving stream headers
			data = self.sock.recv(size - recvd_size)
			if not data:
				break
			recvd_chunks.append(data)
			recvd_size += len(data)
		
		return "".join(recvd_chunks)
	
	def _recv_size_timeout(self, size, timeout):
		#XXX: blargh
		self.sock.setblocking(0)
		recvd_chunks = []
		recvd_size = 0
		
		begin=time.time()
		while True:
			if recvd_size == size:
				break
			if recvd_chunks and time.time()-begin > timeout:
				break
			if time.time()-begin > timeout*2:
				break
			
			try:
				#XXX: this is broken for server receiving stream headers
				data = self.sock.recv(size - recvd_size)
				if data:
					recvd_chunks.append(data)
					recvd_size += len(data)
					begin = time.time()
				else:
					time.sleep(0.1)
			except socket.error:
				pass
		
		#XXX: should non-blocking just be the default?
		self.sock.setblocking(1)
		return "".join(recvd_chunks)
	

	def recv(self, size, timeout=0):
		if not self.sock:
			#XXX: do nothing? throw an exception?
			return ""
		
		data = ""
		if timeout:
			data = self._recv_size_timeout(size, timeout)
		else:
			data = self._recv_size(size)
		
		if self.decrypt_method:
			data = self.decrypt_method(data)
		
		return data


class ACPClientSession(_ACPSession):
	def enable_encryption(self, key, client_iv, server_iv):
		self.encryption_context = ACPEncryption(key, client_iv, server_iv)
		
		self.encrypt_method = encryption_context.client_encrypt
		self.decrypt_method = encryption_context.server_decrypt


class ACPServerSession(_ACPSession):
	def enable_encryption(self, key, client_iv, server_iv):
		self.encryption_context = ACPEncryption(key, client_iv, server_iv)
		
		self.encrypt_method = self.encryption_context.server_encrypt
		self.decrypt_method = self.encryption_context.client_decrypt




================================================
FILE: setup.py
================================================
from setuptools import setup

setup(
	name="acp",
	version="1.0",
	description="AirPyrt Tools",
	author="Vince Cali",
	author_email="0x56.0x69.0x6e.0x63.0x65@gmail.com",
	packages=["acp"],
	entry_points = {
		"console_scripts": ["acp=acp.cli:main"],
		},
	install_requires=[
		"pycrypto",
		]
	)
Download .txt
gitextract_nzg9t11t/

├── .gitignore
├── LICENSE
├── README.md
├── acp/
│   ├── __init__.py
│   ├── __main__.py
│   ├── basebinary.py
│   ├── cflbinary.py
│   ├── cli.py
│   ├── clibs/
│   │   ├── AppleSRP.py
│   │   └── __init__.py
│   ├── client.py
│   ├── encryption.py
│   ├── exception.py
│   ├── keystream.py
│   ├── message.py
│   ├── misc.py
│   ├── property.py
│   └── session.py
└── setup.py
Download .txt
SYMBOL INDEX (148 symbols across 12 files)

FILE: acp/basebinary.py
  function _derive_key (line 35) | def _derive_key(model):
  class BasebinaryError (line 46) | class BasebinaryError(Exception):
  class Basebinary (line 49) | class Basebinary(object):
    method parse (line 58) | def parse(cls, data):
    method compose (line 85) | def compose(cls, data):
    method parse_header (line 91) | def parse_header(cls, data):
    method compose_header (line 101) | def compose_header(cls, byte_0x0F, model, version, byte_0x18, byte_0x1...
    method decrypt (line 107) | def decrypt(cls, data, model, byte_0x0F):
    method decrypt_chunk (line 128) | def decrypt_chunk(cls, encrypted_data, key, iv):
    method encrypt (line 149) | def encrypt(cls, data):
    method extract (line 155) | def extract(cls, data):

FILE: acp/cflbinary.py
  function _lslice (line 13) | def _lslice(data, length):
  class CFLBinaryPListComposeError (line 22) | class CFLBinaryPListComposeError(Exception):
  class CFLBinaryPListParseError (line 26) | class CFLBinaryPListParseError(Exception):
  class CFLBinaryPListComposer (line 31) | class CFLBinaryPListComposer(object):
    method _pack_object (line 35) | def _pack_object(cls, obj):
    method compose (line 134) | def compose(cls, object):
  class CFLBinaryPListParser (line 150) | class CFLBinaryPListParser(object):
    method _unpack_int (line 154) | def _unpack_int(cls, size_exponent, data):
    method _unpack_real (line 185) | def _unpack_real(cls, size_exponent, data):
    method _unpack_count (line 211) | def _unpack_count(cls, object_info, data):
    method _unpack_object_marker (line 234) | def _unpack_object_marker(cls, data):
    method _unpack_object (line 252) | def _unpack_object(cls, data):
    method parse (line 349) | def parse(cls, data):

FILE: acp/cli.py
  class _ArgParser (line 15) | class _ArgParser(argparse.ArgumentParser):
    method error (line 16) | def error(self, message):
  function _cmd_not_implemented (line 22) | def _cmd_not_implemented(*unused):
  function _cmd_listprop (line 25) | def _cmd_listprop(unused):
  function _cmd_helpprop (line 32) | def _cmd_helpprop(args):
  function _cmd_getprop (line 44) | def _cmd_getprop(client, args):
  function _cmd_setprop (line 50) | def _cmd_setprop(client, args):
  function _cmd_dumpprop (line 76) | def _cmd_dumpprop(client, unused):
  function _cmd_acpprop (line 83) | def _cmd_acpprop(client, unused):
  function _cmd_dump_syslog (line 91) | def _cmd_dump_syslog(client, unused):
  function _cmd_reboot (line 94) | def _cmd_reboot(client, unused):
  function _cmd_factory_reset (line 98) | def _cmd_factory_reset(client, unused):
  function _cmd_flash_primary (line 102) | def _cmd_flash_primary(client, args):
  function _cmd_do_feat_command (line 112) | def _cmd_do_feat_command(client, unused):
  function _cmd_decrypt (line 115) | def _cmd_decrypt(args):
  function _cmd_extract (line 129) | def _cmd_extract(args):
  function _cmd_srp_test (line 143) | def _cmd_srp_test(client, unused):
  function main (line 149) | def main():

FILE: acp/clibs/AppleSRP.py
  function _fmt_void_ptr (line 5) | def _fmt_void_ptr(value):
  function _fmt_cstr (line 10) | def _fmt_cstr(value):
  function _fmt_ccz_class (line 15) | def _fmt_ccz_class(value):
  function _fmt_ccz (line 20) | def _fmt_ccz(value):
  class cstr (line 26) | class cstr(Structure):
    method __str__ (line 33) | def __str__(self):
    method get_data_buffer (line 42) | def get_data_buffer(self):
  class SHA1_CTX (line 55) | class SHA1_CTX(Structure):
    method __str__ (line 66) | def __str__(self):
  class client_meth_st (line 89) | class client_meth_st(Structure):
    method __str__ (line 94) | def __str__(self):
  class server_meth_st (line 112) | class server_meth_st(Structure):
    method __str__ (line 120) | def __str__(self):
  class ccz_class (line 139) | class ccz_class(Structure):
    method __str__ (line 145) | def __str__(self):
  class ccz (line 163) | class ccz(Structure):
    method __str__ (line 169) | def __str__(self):
  class srp_st (line 211) | class srp_st(Structure):
    method __str__ (line 235) | def __str__(self):

FILE: acp/client.py
  class ACPClient (line 12) | class ACPClient(object):
    method __init__ (line 13) | def __init__(self, target, password=""):
    method connect (line 20) | def connect(self):
    method close (line 24) | def close(self):
    method send (line 28) | def send(self, data):
    method recv (line 32) | def recv(self, size):
    method recv_message_header (line 36) | def recv_message_header(self):
    method recv_property_element_header (line 40) | def recv_property_element_header(self):
    method get_properties (line 44) | def get_properties(self, prop_names=[]):
    method set_properties (line 91) | def set_properties(self, props_dict={}):
    method get_features (line 129) | def get_features(self):
    method flash_primary (line 139) | def flash_primary(self, payload):
    method authenticate_AppleSRP (line 147) | def authenticate_AppleSRP(self):

FILE: acp/encryption.py
  class _ACPEncryptionContext (line 10) | class _ACPEncryptionContext(object):
    method __init__ (line 11) | def __init__(self, key, iv):
  class ACPEncryption (line 19) | class ACPEncryption(object):
    method __init__ (line 20) | def __init__(self, key, client_iv, server_iv):
    method _init_client_context (line 24) | def _init_client_context(cls, key, iv):
    method _init_server_context (line 28) | def _init_server_context(cls, key, iv):
    method client_decrypt (line 33) | def client_decrypt(self, data):
    method client_encrypt (line 37) | def client_encrypt(self, data):
    method server_decrypt (line 41) | def server_decrypt(self, data):
    method server_encrypt (line 45) | def server_encrypt(self, data):

FILE: acp/exception.py
  class ACPError (line 3) | class ACPError(Exception):
  class ACPClientError (line 8) | class ACPClientError(ACPError):
  class ACPCommandLineError (line 13) | class ACPCommandLineError(ACPError):
  class ACPMessageError (line 18) | class ACPMessageError(ACPError):
  class ACPPropertyError (line 23) | class ACPPropertyError(ACPError):

FILE: acp/keystream.py
  function generate_acp_keystream (line 4) | def generate_acp_keystream(length):

FILE: acp/message.py
  function _generate_acp_header_key (line 9) | def _generate_acp_header_key(password):
  class ACPMessage (line 35) | class ACPMessage(object):
    method __init__ (line 46) | def __init__(self, version, flags, unused, command, error_code, key, b...
    method __str__ (line 67) | def __str__(self):
    method parse_raw (line 80) | def parse_raw(cls, data):
    method compose_echo_command (line 133) | def compose_echo_command(cls, flags, password, payload):
    method compose_flash_primary_command (line 138) | def compose_flash_primary_command(cls, flags, password, payload):
    method compose_flash_secondary_command (line 143) | def compose_flash_secondary_command(cls, flags, password, payload):
    method compose_flash_bootloader_command (line 148) | def compose_flash_bootloader_command(cls, flags, password, payload):
    method compose_getprop_command (line 153) | def compose_getprop_command(cls, flags, password, payload):
    method compose_setprop_command (line 158) | def compose_setprop_command(cls, flags, password, payload):
    method compose_perform_command (line 163) | def compose_perform_command(cls, flags, password, payload):
    method compose_monitor_command (line 168) | def compose_monitor_command(cls, flags, password, payload):
    method compose_rpc_command (line 173) | def compose_rpc_command(cls, flags, password, payload):
    method compose_auth_command (line 178) | def compose_auth_command(cls, flags, payload):
    method compose_feat_command (line 183) | def compose_feat_command(cls, flags):
    method compose_message_ex (line 188) | def compose_message_ex(cls, version, flags, unused, command, error_cod...
    method _compose_raw_packet (line 192) | def _compose_raw_packet(self):
    method _compose_header (line 206) | def _compose_header(self):

FILE: acp/misc.py
  function cast_u32 (line 3) | def cast_u32(value):

FILE: acp/property.py
  function _generate_acp_property_dict (line 553) | def _generate_acp_property_dict():
  class ACPPropertyInitValueError (line 564) | class ACPPropertyInitValueError(ACPPropertyError):
  class ACPProperty (line 568) | class ACPProperty(object):
    method __init__ (line 575) | def __init__(self, name=None, value=None):
    method _init_dec (line 606) | def _init_dec(self, value):
    method _init_hex (line 617) | def _init_hex(self, value):
    method _init_mac (line 628) | def _init_mac(self, value):
    method _init_bin (line 645) | def _init_bin(self, value):
    method _init_cfb (line 651) | def _init_cfb(self, value):
    method _init_log (line 657) | def _init_log(self, value):
    method _init_str (line 663) | def _init_str(self, value):
    method __repr__ (line 670) | def __repr__(self):
    method __str__ (line 676) | def __str__(self):
    method _format_dec (line 686) | def _format_dec(self, value):
    method _format_hex (line 689) | def _format_hex(self, value):
    method _format_mac (line 692) | def _format_mac(self, value):
    method _format_bin (line 698) | def _format_bin(self, value):
    method _format_cfb (line 701) | def _format_cfb(self, value):
    method _format_log (line 704) | def _format_log(self, value):
    method _format_str (line 710) | def _format_str(self, value):
    method get_supported_property_names (line 715) | def get_supported_property_names(cls):
    method get_property_info_string (line 723) | def get_property_info_string(cls, prop_name, key):
    method parse_raw_element (line 738) | def parse_raw_element(cls, data):
    method parse_raw_element_header (line 745) | def parse_raw_element_header(cls, data):
    method compose_raw_element (line 753) | def compose_raw_element(cls, flags, property):
    method compose_raw_element_header (line 769) | def compose_raw_element_header(cls, name, flags, size):

FILE: acp/session.py
  class _ACPSession (line 9) | class _ACPSession(object):
    method __init__ (line 10) | def __init__(self, target, password):
    method connect (line 26) | def connect(self, port=ACP_SERVER_PORT):
    method close (line 33) | def close(self):
    method send (line 39) | def send(self, data):
    method _recv_size (line 48) | def _recv_size(self, size):
    method _recv_size_timeout (line 63) | def _recv_size_timeout(self, size, timeout):
    method recv (line 95) | def recv(self, size, timeout=0):
  class ACPClientSession (line 112) | class ACPClientSession(_ACPSession):
    method enable_encryption (line 113) | def enable_encryption(self, key, client_iv, server_iv):
  class ACPServerSession (line 120) | class ACPServerSession(_ACPSession):
    method enable_encryption (line 121) | def enable_encryption(self, key, client_iv, server_iv):
Condensed preview — 19 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (92K chars).
[
  {
    "path": ".gitignore",
    "chars": 41,
    "preview": ".DS_Store\n\n*.pyc\n*.egg-info\nbuild/\ndist/\n"
  },
  {
    "path": "LICENSE",
    "chars": 1053,
    "preview": "Copyright (c) 2016 Vince Cali\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this soft"
  },
  {
    "path": "README.md",
    "chars": 3854,
    "preview": "# AirPyrt Tools\n\n### License\n\nSee LICENSE\n\n\n### Requirements\n\n- python 2.7\n- pycrypto\n\n\n### Installation\n\n`python setup."
  },
  {
    "path": "acp/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "acp/__main__.py",
    "chars": 22,
    "preview": "import cli\ncli.main()\n"
  },
  {
    "path": "acp/basebinary.py",
    "chars": 4396,
    "preview": "import logging\nimport os.path\nimport struct\nimport zlib\n\nfrom Crypto.Cipher import AES\n\n#XXX: ugh...\nfrom .misc import c"
  },
  {
    "path": "acp/cflbinary.py",
    "chars": 9185,
    "preview": "import logging\nimport struct\nfrom collections import OrderedDict\nfrom math  import log\nfrom types import *\n\n\n_header_mag"
  },
  {
    "path": "acp/cli.py",
    "chars": 8406,
    "preview": "import argparse\nimport logging\nimport os.path\nimport sys\nimport time\n\nfrom collections import OrderedDict\n\nfrom .basebin"
  },
  {
    "path": "acp/clibs/AppleSRP.py",
    "chars": 9675,
    "preview": "from ctypes import *\n\n\n#XXX: hax to display NULL pointer thingies\ndef _fmt_void_ptr(value):\n\tif value is None:\n\t\treturn "
  },
  {
    "path": "acp/clibs/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "acp/client.py",
    "chars": 8355,
    "preview": "import logging\nimport os\nimport struct\nimport time\n\nfrom .cflbinary import CFLBinaryPListComposer, CFLBinaryPListParser\n"
  },
  {
    "path": "acp/encryption.py",
    "chars": 1310,
    "preview": "from Crypto.Cipher import AES\nfrom Crypto.Protocol import KDF\nfrom Crypto.Util import Counter\n\n\nPBKDF_salt0 = \"F072FA3F6"
  },
  {
    "path": "acp/exception.py",
    "chars": 522,
    "preview": "#TODO: put other exceptions in here...\n\nclass ACPError(Exception):\n\t\"\"\"Base class for exceptions in this module.\"\"\"\n\tpas"
  },
  {
    "path": "acp/keystream.py",
    "chars": 540,
    "preview": "\"\"\"Static key/seed for keystream generation\"\"\"\nACP_STATIC_KEY = \"5b6faf5d9d5b0e1351f2da1de7e8d673\".decode(\"hex\")\n\ndef ge"
  },
  {
    "path": "acp/message.py",
    "chars": 8201,
    "preview": "import logging\nimport struct\nimport zlib\n\nfrom .exception import ACPMessageError\nfrom .keystream import *\n\n\ndef _generat"
  },
  {
    "path": "acp/misc.py",
    "chars": 247,
    "preview": "#XXX: this file exists until I think of a better way to do this\n\ndef cast_u32(value):\n\t#XXX: lazy, how do we do this cor"
  },
  {
    "path": "acp/property.py",
    "chars": 20415,
    "preview": "import logging\nimport pprint\nimport struct\n\nfrom .cflbinary import CFLBinaryPListParser\nfrom .exception import ACPProper"
  },
  {
    "path": "acp/session.py",
    "chars": 2932,
    "preview": "import logging\nimport socket\n\nfrom .encryption import ACPEncryption\n\n\nACP_SERVER_PORT = 5009\n\nclass _ACPSession(object):"
  },
  {
    "path": "setup.py",
    "chars": 296,
    "preview": "from setuptools import setup\n\nsetup(\n\tname=\"acp\",\n\tversion=\"1.0\",\n\tdescription=\"AirPyrt Tools\",\n\tauthor=\"Vince Cali\",\n\ta"
  }
]

About this extraction

This page contains the full source code of the x56/airpyrt-tools GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 19 files (77.6 KB), approximately 23.5k tokens, and a symbol index with 148 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!