aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mcrute@gmail.com>2013-09-23 13:46:04 -0400
committerMike Crute <mcrute@gmail.com>2013-09-23 13:46:04 -0400
commit07a941dd1f316a4facb97e2841764f5244225d95 (patch)
treefd101cbbcd638bf54414e974f63a32479eef6c34
parent8a80d26c5141dd2f6d31041917a1ceb2cf25df52 (diff)
downloadubntmfi-07a941dd1f316a4facb97e2841764f5244225d95.tar.bz2
ubntmfi-07a941dd1f316a4facb97e2841764f5244225d95.tar.xz
ubntmfi-07a941dd1f316a4facb97e2841764f5244225d95.zip
Add inform server
-rw-r--r--inform.py49
-rw-r--r--inform_server.py45
-rw-r--r--requirements.txt1
3 files changed, 83 insertions, 12 deletions
diff --git a/inform.py b/inform.py
index 1af39e6..6741415 100644
--- a/inform.py
+++ b/inform.py
@@ -1,4 +1,5 @@
1import json 1import json
2import copy
2import struct 3import struct
3import binascii 4import binascii
4from Crypto.Cipher import AES 5from Crypto.Cipher import AES
@@ -89,6 +90,17 @@ class InformPacket(object):
89 self.data_version = None 90 self.data_version = None
90 self.data_length = None 91 self.data_length = None
91 self.raw_payload = None 92 self.raw_payload = None
93 self._used_key = None
94
95 def response_copy(self):
96 """Copy object for use in response
97
98 Generates a deep copy of the object and removes the payload so that it
99 can be used to respond to the station that send this inform request.
100 """
101 new_obj = copy.deepcopy(self)
102 new_obj.raw_payload = None
103 return new_obj
92 104
93 @staticmethod 105 @staticmethod
94 def _format_mac_addr(mac_bytes): 106 def _format_mac_addr(mac_bytes):
@@ -112,10 +124,14 @@ class InformPacket(object):
112 @property 124 @property
113 def payload(self): 125 def payload(self):
114 if self.data_version == 1: 126 if self.data_version == 1:
115 return json.loads(self.raw_payload) 127 return json.loads(self.raw_payload.decode("latin-1"))
116 else: 128 else:
117 return self.raw_payload 129 return self.raw_payload
118 130
131 @payload.setter
132 def payload(self, value):
133 self.raw_payload = json.dumps(value)
134
119 135
120class InformSerializer(object): 136class InformSerializer(object):
121 """Inform protocol version 1 parser/serializer 137 """Inform protocol version 1 parser/serializer
@@ -130,32 +146,41 @@ class InformSerializer(object):
130 PROTOCOL_MAGIC = 1414414933 146 PROTOCOL_MAGIC = 1414414933
131 MAX_VERSION = 1 147 MAX_VERSION = 1
132 148
133 def __init__(self, key=MASTER_KEY): 149 def __init__(self, key=None, key_bag=None):
134 self.key = key 150 self.key = key
135 self._used_key = None 151 self.key_bag = key_bag or {}
136 152
137 def _decrypt_payload(self, packet): 153 def _decrypt_payload(self, packet):
138 if not packet.is_encrypted: 154 if not packet.is_encrypted:
139 return 155 return
140 156
141 for key in (self.key, self.MASTER_KEY): 157 i = 0
158 key = self.key_bag.get(packet.formatted_mac_addr)
159
160 for key in (key, self.key, self.MASTER_KEY):
161 if key is None:
162 continue
163
142 decrypted = Cryptor(key, packet.iv).decrypt(packet.raw_payload) 164 decrypted = Cryptor(key, packet.iv).decrypt(packet.raw_payload)
143 165
144 try: 166 try:
145 json.loads(decrypted) 167 json.loads(decrypted.decode("latin-1"))
146 packet.raw_payload = decrypted 168 packet.raw_payload = decrypted
147 self._used_key = key 169 packet._used_key = key
148 break 170 break
149 except ValueError: 171 except ValueError as err:
150 continue 172 if err.message == "No JSON object could be decoded":
173 continue
174 else:
175 raise
151 176
152 def parse(self, input): 177 def parse(self, input):
153 input_stream = BinaryDataStream(input) 178 input_stream = BinaryDataStream(input)
154 179
155 packet = InformPacket() 180 packet = InformPacket()
156 181
157 packet.magic = input_stream.read_int() 182 packet.magic_number = input_stream.read_int()
158 assert packet.magic == self.PROTOCOL_MAGIC 183 assert packet.magic_number == self.PROTOCOL_MAGIC
159 184
160 packet.version = input_stream.read_int() 185 packet.version = input_stream.read_int()
161 assert packet.version < self.MAX_VERSION 186 assert packet.version < self.MAX_VERSION
@@ -175,13 +200,13 @@ class InformSerializer(object):
175 if packet.data_version != 1: 200 if packet.data_version != 1:
176 raise ValueError("Can no encrypt contents of pre 1.0 packets") 201 raise ValueError("Can no encrypt contents of pre 1.0 packets")
177 202
178 key = self._used_key if self._used_key else self.MASTER_KEY 203 key = packet._used_key if packet._used_key else self.MASTER_KEY
179 return Cryptor(key, packet.iv).encrypt(json.dumps(packet.payload)) 204 return Cryptor(key, packet.iv).encrypt(json.dumps(packet.payload))
180 205
181 def serialize(self, packet): 206 def serialize(self, packet):
182 output = BinaryDataStream.for_output() 207 output = BinaryDataStream.for_output()
183 208
184 output.write_int(packet.magic) 209 output.write_int(packet.magic_number)
185 output.write_int(packet.version) 210 output.write_int(packet.version)
186 output.write_string(packet.mac_addr) 211 output.write_string(packet.mac_addr)
187 output.write_short(packet.flags) 212 output.write_short(packet.flags)
diff --git a/inform_server.py b/inform_server.py
new file mode 100644
index 0000000..ebe3bda
--- /dev/null
+++ b/inform_server.py
@@ -0,0 +1,45 @@
1#!/usr/bin/python
2
3from StringIO import StringIO
4from SocketServer import TCPServer
5from inform import InformSerializer
6from ConfigParser import SafeConfigParser
7from SimpleHTTPServer import SimpleHTTPRequestHandler
8
9
10class Handler(SimpleHTTPRequestHandler):
11
12 def __init__(self, *args, **kwargs):
13 super(SimpleHTTPServer, self).__init__(*args, **kwargs)
14 self.parser = InformSerializer(key_bag=get_keys("inform.cfg"))
15
16 def _get_keys(self, filename):
17 cfg = SafeConfigParser()
18 cfg.read(filename)
19
20 return dict((sect, cfg.get(sect, "authkey"))
21 for sect in cfg.sections())
22
23 def do_POST(self):
24 length = int(self.headers['content-length'])
25 body = StringIO(self.rfile.read(length))
26 packet = self.parser.parse(body)
27
28 noop_packet = packet.response_copy()
29 noop_packet.payload = { "_type": "noop", "interval": 10 }
30
31 buffer = StringIO(self.parser.serialize(noop_packet))
32
33 self.send_response(200)
34 self.send_header("Content-type", "application/x-binary")
35 self.send_header("Connection", "close")
36 self.send_header("User-Agent", "Unifi Controller")
37 self.send_header("Content-Length", buffer.len)
38 self.end_headers()
39
40 return buffer
41
42
43httpd = TCPServer(("", 9966), Handler)
44print "serving on 9966"
45httpd.serve_forever()
diff --git a/requirements.txt b/requirements.txt
index 953bf15..390e1fe 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1,2 @@
1tornado==3.1 1tornado==3.1
2pycrypto==2.6