diff options
author | Mike Crute <mcrute@gmail.com> | 2013-09-23 13:46:04 -0400 |
---|---|---|
committer | Mike Crute <mcrute@gmail.com> | 2013-09-23 13:46:04 -0400 |
commit | 07a941dd1f316a4facb97e2841764f5244225d95 (patch) | |
tree | fd101cbbcd638bf54414e974f63a32479eef6c34 | |
parent | 8a80d26c5141dd2f6d31041917a1ceb2cf25df52 (diff) | |
download | ubntmfi-07a941dd1f316a4facb97e2841764f5244225d95.tar.bz2 ubntmfi-07a941dd1f316a4facb97e2841764f5244225d95.tar.xz ubntmfi-07a941dd1f316a4facb97e2841764f5244225d95.zip |
Add inform server
-rw-r--r-- | inform.py | 49 | ||||
-rw-r--r-- | inform_server.py | 45 | ||||
-rw-r--r-- | requirements.txt | 1 |
3 files changed, 83 insertions, 12 deletions
@@ -1,4 +1,5 @@ | |||
1 | import json | 1 | import json |
2 | import copy | ||
2 | import struct | 3 | import struct |
3 | import binascii | 4 | import binascii |
4 | from Crypto.Cipher import AES | 5 | from 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 | ||
120 | class InformSerializer(object): | 136 | class 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 | |||
3 | from StringIO import StringIO | ||
4 | from SocketServer import TCPServer | ||
5 | from inform import InformSerializer | ||
6 | from ConfigParser import SafeConfigParser | ||
7 | from SimpleHTTPServer import SimpleHTTPRequestHandler | ||
8 | |||
9 | |||
10 | class 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 | |||
43 | httpd = TCPServer(("", 9966), Handler) | ||
44 | print "serving on 9966" | ||
45 | httpd.serve_forever() | ||
diff --git a/requirements.txt b/requirements.txt index 953bf15..390e1fe 100644 --- a/requirements.txt +++ b/requirements.txt | |||
@@ -1 +1,2 @@ | |||
1 | tornado==3.1 | 1 | tornado==3.1 |
2 | pycrypto==2.6 | ||