]>
Commit | Line | Data |
---|---|---|
68f51f9a JM |
1 | #!/usr/bin/python |
2 | # | |
3 | # Example nfcpy to hostapd wrapper for WPS NFC operations | |
4 | # Copyright (c) 2012-2013, Jouni Malinen <j@w1.fi> | |
5 | # | |
6 | # This software may be distributed under the terms of the BSD license. | |
7 | # See README for more details. | |
8 | ||
9 | import os | |
10 | import sys | |
11 | import time | |
ab1db08c | 12 | import argparse |
68f51f9a JM |
13 | |
14 | import nfc | |
15 | import nfc.ndef | |
16 | import nfc.llcp | |
17 | import nfc.handover | |
18 | ||
8140ae96 | 19 | import logging |
8140ae96 | 20 | |
c3aa4da9 | 21 | import wpaspy |
68f51f9a JM |
22 | |
23 | wpas_ctrl = '/var/run/hostapd' | |
6f8fa6e5 | 24 | continue_loop = True |
d6bfaaac JM |
25 | summary_file = None |
26 | success_file = None | |
27 | ||
28 | def summary(txt): | |
89896c00 | 29 | print(txt) |
d6bfaaac JM |
30 | if summary_file: |
31 | with open(summary_file, 'a') as f: | |
32 | f.write(txt + "\n") | |
33 | ||
34 | def success_report(txt): | |
35 | summary(txt) | |
36 | if success_file: | |
37 | with open(success_file, 'a') as f: | |
38 | f.write(txt + "\n") | |
68f51f9a JM |
39 | |
40 | def wpas_connect(): | |
41 | ifaces = [] | |
42 | if os.path.isdir(wpas_ctrl): | |
43 | try: | |
44 | ifaces = [os.path.join(wpas_ctrl, i) for i in os.listdir(wpas_ctrl)] | |
bab493b9 | 45 | except OSError as error: |
89896c00 | 46 | print("Could not find hostapd: ", error) |
68f51f9a JM |
47 | return None |
48 | ||
49 | if len(ifaces) < 1: | |
89896c00 | 50 | print("No hostapd control interface found") |
68f51f9a JM |
51 | return None |
52 | ||
53 | for ctrl in ifaces: | |
54 | try: | |
c3aa4da9 | 55 | wpas = wpaspy.Ctrl(ctrl) |
68f51f9a | 56 | return wpas |
bab493b9 | 57 | except Exception as e: |
68f51f9a JM |
58 | pass |
59 | return None | |
60 | ||
61 | ||
62 | def wpas_tag_read(message): | |
63 | wpas = wpas_connect() | |
64 | if (wpas == None): | |
d6bfaaac | 65 | return False |
6f8fa6e5 JM |
66 | if "FAIL" in wpas.request("WPS_NFC_TAG_READ " + str(message).encode("hex")): |
67 | return False | |
68 | return True | |
68f51f9a JM |
69 | |
70 | ||
71 | def wpas_get_config_token(): | |
72 | wpas = wpas_connect() | |
73 | if (wpas == None): | |
74 | return None | |
d6bfaaac JM |
75 | ret = wpas.request("WPS_NFC_CONFIG_TOKEN NDEF") |
76 | if "FAIL" in ret: | |
77 | return None | |
78 | return ret.rstrip().decode("hex") | |
68f51f9a JM |
79 | |
80 | ||
81 | def wpas_get_password_token(): | |
82 | wpas = wpas_connect() | |
83 | if (wpas == None): | |
84 | return None | |
d6bfaaac JM |
85 | ret = wpas.request("WPS_NFC_TOKEN NDEF") |
86 | if "FAIL" in ret: | |
87 | return None | |
88 | return ret.rstrip().decode("hex") | |
68f51f9a JM |
89 | |
90 | ||
51e985dd | 91 | def wpas_get_handover_sel(): |
68f51f9a JM |
92 | wpas = wpas_connect() |
93 | if (wpas == None): | |
94 | return None | |
d6bfaaac JM |
95 | ret = wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR") |
96 | if "FAIL" in ret: | |
97 | return None | |
98 | return ret.rstrip().decode("hex") | |
68f51f9a JM |
99 | |
100 | ||
e4758827 | 101 | def wpas_report_handover(req, sel): |
68f51f9a JM |
102 | wpas = wpas_connect() |
103 | if (wpas == None): | |
e4758827 JM |
104 | return None |
105 | return wpas.request("NFC_REPORT_HANDOVER RESP WPS " + | |
106 | str(req).encode("hex") + " " + | |
107 | str(sel).encode("hex")) | |
68f51f9a JM |
108 | |
109 | ||
51e985dd | 110 | class HandoverServer(nfc.handover.HandoverServer): |
6f8fa6e5 JM |
111 | def __init__(self, llc): |
112 | super(HandoverServer, self).__init__(llc) | |
113 | self.ho_server_processing = False | |
114 | self.success = False | |
68f51f9a | 115 | |
7ae7a84e JM |
116 | # override to avoid parser error in request/response.pretty() in nfcpy |
117 | # due to new WSC handover format | |
118 | def _process_request(self, request): | |
119 | summary("received handover request {}".format(request.type)) | |
120 | response = nfc.ndef.Message("\xd1\x02\x01Hs\x12") | |
121 | if not request.type == 'urn:nfc:wkt:Hr': | |
122 | summary("not a handover request") | |
123 | else: | |
124 | try: | |
125 | request = nfc.ndef.HandoverRequestMessage(request) | |
126 | except nfc.ndef.DecodeError as e: | |
127 | summary("error decoding 'Hr' message: {}".format(e)) | |
128 | else: | |
129 | response = self.process_request(request) | |
130 | summary("send handover response {}".format(response.type)) | |
131 | return response | |
132 | ||
51e985dd | 133 | def process_request(self, request): |
d6bfaaac | 134 | summary("HandoverServer - request received") |
12288d84 | 135 | try: |
89896c00 | 136 | print("Parsed handover request: " + request.pretty()) |
bab493b9 | 137 | except Exception as e: |
89896c00 MH |
138 | print(e) |
139 | print(str(request).encode("hex")) | |
51e985dd JM |
140 | |
141 | sel = nfc.ndef.HandoverSelectMessage(version="1.2") | |
142 | ||
143 | for carrier in request.carriers: | |
89896c00 | 144 | print("Remote carrier type: " + carrier.type) |
51e985dd | 145 | if carrier.type == "application/vnd.wfa.wsc": |
d6bfaaac | 146 | summary("WPS carrier type match - add WPS carrier record") |
51e985dd JM |
147 | data = wpas_get_handover_sel() |
148 | if data is None: | |
d6bfaaac | 149 | summary("Could not get handover select carrier record from hostapd") |
51e985dd | 150 | continue |
89896c00 MH |
151 | print("Handover select carrier record from hostapd:") |
152 | print(data.encode("hex")) | |
d6bfaaac JM |
153 | if "OK" in wpas_report_handover(carrier.record, data): |
154 | success_report("Handover reported successfully") | |
155 | else: | |
156 | summary("Handover report rejected") | |
51e985dd JM |
157 | |
158 | message = nfc.ndef.Message(data); | |
159 | sel.add_carrier(message[0], "active", message[1:]) | |
160 | ||
89896c00 | 161 | print("Handover select:") |
12288d84 | 162 | try: |
89896c00 | 163 | print(sel.pretty()) |
bab493b9 | 164 | except Exception as e: |
89896c00 MH |
165 | print(e) |
166 | print(str(sel).encode("hex")) | |
51e985dd | 167 | |
d6bfaaac | 168 | summary("Sending handover select") |
6f8fa6e5 | 169 | self.success = True |
51e985dd JM |
170 | return sel |
171 | ||
172 | ||
68f51f9a | 173 | def wps_tag_read(tag): |
6f8fa6e5 | 174 | success = False |
68f51f9a | 175 | if len(tag.ndef.message): |
6f8fa6e5 | 176 | for record in tag.ndef.message: |
89896c00 | 177 | print("record type " + record.type) |
68f51f9a | 178 | if record.type == "application/vnd.wfa.wsc": |
d6bfaaac | 179 | summary("WPS tag - send to hostapd") |
6f8fa6e5 | 180 | success = wpas_tag_read(tag.ndef.message) |
68f51f9a JM |
181 | break |
182 | else: | |
d6bfaaac JM |
183 | summary("Empty tag") |
184 | ||
185 | if success: | |
186 | success_report("Tag read succeeded") | |
68f51f9a | 187 | |
6f8fa6e5 | 188 | return success |
68f51f9a JM |
189 | |
190 | ||
6f8fa6e5 | 191 | def rdwr_connected_write(tag): |
d6bfaaac | 192 | summary("Tag found - writing - " + str(tag)) |
6f8fa6e5 JM |
193 | global write_data |
194 | tag.ndef.message = str(write_data) | |
d6bfaaac | 195 | success_report("Tag write succeeded") |
89896c00 | 196 | print("Done - remove tag") |
6f8fa6e5 JM |
197 | global only_one |
198 | if only_one: | |
199 | global continue_loop | |
200 | continue_loop = False | |
201 | global write_wait_remove | |
202 | while write_wait_remove and tag.is_present: | |
203 | time.sleep(0.1) | |
204 | ||
1f1b5b31 | 205 | def wps_write_config_tag(clf, wait_remove=True): |
d6bfaaac | 206 | summary("Write WPS config token") |
6f8fa6e5 | 207 | global write_data, write_wait_remove |
1f1b5b31 | 208 | write_wait_remove = wait_remove |
6f8fa6e5 JM |
209 | write_data = wpas_get_config_token() |
210 | if write_data == None: | |
d6bfaaac | 211 | summary("Could not get WPS config token from hostapd") |
68f51f9a JM |
212 | return |
213 | ||
89896c00 | 214 | print("Touch an NFC tag") |
6f8fa6e5 | 215 | clf.connect(rdwr={'on-connect': rdwr_connected_write}) |
68f51f9a JM |
216 | |
217 | ||
1f1b5b31 | 218 | def wps_write_password_tag(clf, wait_remove=True): |
d6bfaaac | 219 | summary("Write WPS password token") |
6f8fa6e5 | 220 | global write_data, write_wait_remove |
1f1b5b31 | 221 | write_wait_remove = wait_remove |
6f8fa6e5 JM |
222 | write_data = wpas_get_password_token() |
223 | if write_data == None: | |
d6bfaaac | 224 | summary("Could not get WPS password token from hostapd") |
68f51f9a JM |
225 | return |
226 | ||
89896c00 | 227 | print("Touch an NFC tag") |
6f8fa6e5 JM |
228 | clf.connect(rdwr={'on-connect': rdwr_connected_write}) |
229 | ||
230 | ||
231 | def rdwr_connected(tag): | |
1f1b5b31 | 232 | global only_one, no_wait |
d6bfaaac | 233 | summary("Tag connected: " + str(tag)) |
6f8fa6e5 JM |
234 | |
235 | if tag.ndef: | |
89896c00 | 236 | print("NDEF tag: " + tag.type) |
6f8fa6e5 | 237 | try: |
89896c00 | 238 | print(tag.ndef.message.pretty()) |
bab493b9 | 239 | except Exception as e: |
89896c00 | 240 | print(e) |
6f8fa6e5 JM |
241 | success = wps_tag_read(tag) |
242 | if only_one and success: | |
243 | global continue_loop | |
244 | continue_loop = False | |
245 | else: | |
d6bfaaac JM |
246 | summary("Not an NDEF tag - remove tag") |
247 | return True | |
68f51f9a | 248 | |
6f8fa6e5 JM |
249 | return not no_wait |
250 | ||
68f51f9a | 251 | |
6f8fa6e5 | 252 | def llcp_startup(clf, llc): |
89896c00 | 253 | print("Start LLCP server") |
6f8fa6e5 JM |
254 | global srv |
255 | srv = HandoverServer(llc) | |
256 | return llc | |
68f51f9a | 257 | |
6f8fa6e5 | 258 | def llcp_connected(llc): |
89896c00 | 259 | print("P2P LLCP connected") |
6f8fa6e5 JM |
260 | global wait_connection |
261 | wait_connection = False | |
262 | global srv | |
263 | srv.start() | |
264 | return True | |
dc6bda11 JM |
265 | |
266 | ||
68f51f9a JM |
267 | def main(): |
268 | clf = nfc.ContactlessFrontend() | |
269 | ||
ab1db08c | 270 | parser = argparse.ArgumentParser(description='nfcpy to hostapd integration for WPS NFC operations') |
6202500f JM |
271 | parser.add_argument('-d', const=logging.DEBUG, default=logging.INFO, |
272 | action='store_const', dest='loglevel', | |
273 | help='verbose debug output') | |
274 | parser.add_argument('-q', const=logging.WARNING, action='store_const', | |
275 | dest='loglevel', help='be quiet') | |
ab1db08c JM |
276 | parser.add_argument('--only-one', '-1', action='store_true', |
277 | help='run only one operation and exit') | |
1f1b5b31 JM |
278 | parser.add_argument('--no-wait', action='store_true', |
279 | help='do not wait for tag to be removed before exiting') | |
d6bfaaac JM |
280 | parser.add_argument('--summary', |
281 | help='summary file for writing status updates') | |
282 | parser.add_argument('--success', | |
283 | help='success file for writing success update') | |
ab1db08c JM |
284 | parser.add_argument('command', choices=['write-config', |
285 | 'write-password'], | |
286 | nargs='?') | |
287 | args = parser.parse_args() | |
288 | ||
289 | global only_one | |
290 | only_one = args.only_one | |
291 | ||
1f1b5b31 JM |
292 | global no_wait |
293 | no_wait = args.no_wait | |
294 | ||
d6bfaaac JM |
295 | if args.summary: |
296 | global summary_file | |
297 | summary_file = args.summary | |
298 | ||
299 | if args.success: | |
300 | global success_file | |
301 | success_file = args.success | |
302 | ||
6202500f JM |
303 | logging.basicConfig(level=args.loglevel) |
304 | ||
68f51f9a | 305 | try: |
6f8fa6e5 | 306 | if not clf.open("usb"): |
89896c00 | 307 | print("Could not open connection with an NFC device") |
6f8fa6e5 JM |
308 | raise SystemExit |
309 | ||
ab1db08c | 310 | if args.command == "write-config": |
1f1b5b31 | 311 | wps_write_config_tag(clf, wait_remove=not args.no_wait) |
68f51f9a JM |
312 | raise SystemExit |
313 | ||
ab1db08c | 314 | if args.command == "write-password": |
1f1b5b31 | 315 | wps_write_password_tag(clf, wait_remove=not args.no_wait) |
68f51f9a JM |
316 | raise SystemExit |
317 | ||
6f8fa6e5 JM |
318 | global continue_loop |
319 | while continue_loop: | |
89896c00 | 320 | print("Waiting for a tag or peer to be touched") |
6f8fa6e5 JM |
321 | wait_connection = True |
322 | try: | |
323 | if not clf.connect(rdwr={'on-connect': rdwr_connected}, | |
324 | llcp={'on-startup': llcp_startup, | |
325 | 'on-connect': llcp_connected}): | |
326 | break | |
bab493b9 | 327 | except Exception as e: |
89896c00 | 328 | print("clf.connect failed") |
6f8fa6e5 JM |
329 | |
330 | global srv | |
331 | if only_one and srv and srv.success: | |
332 | raise SystemExit | |
68f51f9a JM |
333 | |
334 | except KeyboardInterrupt: | |
335 | raise SystemExit | |
336 | finally: | |
337 | clf.close() | |
338 | ||
339 | raise SystemExit | |
340 | ||
341 | if __name__ == '__main__': | |
342 | main() |