Updated through tag hostap_2_5 from git://w1.fi/hostap.git
[mech_eap.git] / libeap / wpa_supplicant / examples / wps-nfc.py
1 #!/usr/bin/python
2 #
3 # Example nfcpy to wpa_supplicant 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
12 import random
13 import threading
14 import argparse
15
16 import nfc
17 import nfc.ndef
18 import nfc.llcp
19 import nfc.handover
20
21 import logging
22
23 import wpaspy
24
25 wpas_ctrl = '/var/run/wpa_supplicant'
26 srv = None
27 continue_loop = True
28 terminate_now = False
29 summary_file = None
30 success_file = None
31
32 def summary(txt):
33     print txt
34     if summary_file:
35         with open(summary_file, 'a') as f:
36             f.write(txt + "\n")
37
38 def success_report(txt):
39     summary(txt)
40     if success_file:
41         with open(success_file, 'a') as f:
42             f.write(txt + "\n")
43
44 def wpas_connect():
45     ifaces = []
46     if os.path.isdir(wpas_ctrl):
47         try:
48             ifaces = [os.path.join(wpas_ctrl, i) for i in os.listdir(wpas_ctrl)]
49         except OSError, error:
50             print "Could not find wpa_supplicant: ", error
51             return None
52
53     if len(ifaces) < 1:
54         print "No wpa_supplicant control interface found"
55         return None
56
57     for ctrl in ifaces:
58         try:
59             wpas = wpaspy.Ctrl(ctrl)
60             return wpas
61         except Exception, e:
62             pass
63     return None
64
65
66 def wpas_tag_read(message):
67     wpas = wpas_connect()
68     if (wpas == None):
69         return False
70     if "FAIL" in wpas.request("WPS_NFC_TAG_READ " + str(message).encode("hex")):
71         return False
72     return True
73
74 def wpas_get_config_token(id=None):
75     wpas = wpas_connect()
76     if (wpas == None):
77         return None
78     if id:
79         ret = wpas.request("WPS_NFC_CONFIG_TOKEN NDEF " + id)
80     else:
81         ret = wpas.request("WPS_NFC_CONFIG_TOKEN NDEF")
82     if "FAIL" in ret:
83         return None
84     return ret.rstrip().decode("hex")
85
86
87 def wpas_get_er_config_token(uuid):
88     wpas = wpas_connect()
89     if (wpas == None):
90         return None
91     ret = wpas.request("WPS_ER_NFC_CONFIG_TOKEN NDEF " + uuid)
92     if "FAIL" in ret:
93         return None
94     return ret.rstrip().decode("hex")
95
96
97 def wpas_get_password_token():
98     wpas = wpas_connect()
99     if (wpas == None):
100         return None
101     ret = wpas.request("WPS_NFC_TOKEN NDEF")
102     if "FAIL" in ret:
103         return None
104     return ret.rstrip().decode("hex")
105
106 def wpas_get_handover_req():
107     wpas = wpas_connect()
108     if (wpas == None):
109         return None
110     ret = wpas.request("NFC_GET_HANDOVER_REQ NDEF WPS-CR")
111     if "FAIL" in ret:
112         return None
113     return ret.rstrip().decode("hex")
114
115
116 def wpas_get_handover_sel(uuid):
117     wpas = wpas_connect()
118     if (wpas == None):
119         return None
120     if uuid is None:
121         res = wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR").rstrip()
122     else:
123         res = wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR " + uuid).rstrip()
124     if "FAIL" in res:
125         return None
126     return res.decode("hex")
127
128
129 def wpas_report_handover(req, sel, type):
130     wpas = wpas_connect()
131     if (wpas == None):
132         return None
133     return wpas.request("NFC_REPORT_HANDOVER " + type + " WPS " +
134                         str(req).encode("hex") + " " +
135                         str(sel).encode("hex"))
136
137
138 class HandoverServer(nfc.handover.HandoverServer):
139     def __init__(self, llc):
140         super(HandoverServer, self).__init__(llc)
141         self.sent_carrier = None
142         self.ho_server_processing = False
143         self.success = False
144
145     # override to avoid parser error in request/response.pretty() in nfcpy
146     # due to new WSC handover format
147     def _process_request(self, request):
148         summary("received handover request {}".format(request.type))
149         response = nfc.ndef.Message("\xd1\x02\x01Hs\x12")
150         if not request.type == 'urn:nfc:wkt:Hr':
151             summary("not a handover request")
152         else:
153             try:
154                 request = nfc.ndef.HandoverRequestMessage(request)
155             except nfc.ndef.DecodeError as e:
156                 summary("error decoding 'Hr' message: {}".format(e))
157             else:
158                 response = self.process_request(request)
159         summary("send handover response {}".format(response.type))
160         return response
161
162     def process_request(self, request):
163         self.ho_server_processing = True
164         summary("HandoverServer - request received")
165         try:
166             print "Parsed handover request: " + request.pretty()
167         except Exception, e:
168             print e
169
170         sel = nfc.ndef.HandoverSelectMessage(version="1.2")
171
172         for carrier in request.carriers:
173             print "Remote carrier type: " + carrier.type
174             if carrier.type == "application/vnd.wfa.wsc":
175                 summary("WPS carrier type match - add WPS carrier record")
176                 data = wpas_get_handover_sel(self.uuid)
177                 if data is None:
178                     summary("Could not get handover select carrier record from wpa_supplicant")
179                     continue
180                 print "Handover select carrier record from wpa_supplicant:"
181                 print data.encode("hex")
182                 self.sent_carrier = data
183                 if "OK" in wpas_report_handover(carrier.record, self.sent_carrier, "RESP"):
184                     success_report("Handover reported successfully (responder)")
185                 else:
186                     summary("Handover report rejected (responder)")
187
188                 message = nfc.ndef.Message(data);
189                 sel.add_carrier(message[0], "active", message[1:])
190
191         print "Handover select:"
192         try:
193             print sel.pretty()
194         except Exception, e:
195             print e
196         print str(sel).encode("hex")
197
198         summary("Sending handover select")
199         self.success = True
200         return sel
201
202
203 def wps_handover_init(llc):
204     summary("Trying to initiate WPS handover")
205
206     data = wpas_get_handover_req()
207     if (data == None):
208         summary("Could not get handover request carrier record from wpa_supplicant")
209         return
210     print "Handover request carrier record from wpa_supplicant: " + data.encode("hex")
211
212     message = nfc.ndef.HandoverRequestMessage(version="1.2")
213     message.nonce = random.randint(0, 0xffff)
214     datamsg = nfc.ndef.Message(data)
215     message.add_carrier(datamsg[0], "active", datamsg[1:])
216
217     print "Handover request:"
218     try:
219         print message.pretty()
220     except Exception, e:
221         print e
222     print str(message).encode("hex")
223
224     client = nfc.handover.HandoverClient(llc)
225     try:
226         summary("Trying to initiate NFC connection handover")
227         client.connect()
228         summary("Connected for handover")
229     except nfc.llcp.ConnectRefused:
230         summary("Handover connection refused")
231         client.close()
232         return
233     except Exception, e:
234         summary("Other exception: " + str(e))
235         client.close()
236         return
237
238     summary("Sending handover request")
239
240     if not client.send(message):
241         summary("Failed to send handover request")
242         client.close()
243         return
244
245     summary("Receiving handover response")
246     message = client._recv()
247     if message is None:
248         summary("No response received")
249         client.close()
250         return
251     if message.type != "urn:nfc:wkt:Hs":
252         summary("Response was not Hs - received: " + message.type)
253         client.close()
254         return
255
256     print "Received message"
257     try:
258         print message.pretty()
259     except Exception, e:
260         print e
261     print str(message).encode("hex")
262     message = nfc.ndef.HandoverSelectMessage(message)
263     summary("Handover select received")
264     try:
265         print message.pretty()
266     except Exception, e:
267         print e
268
269     for carrier in message.carriers:
270         print "Remote carrier type: " + carrier.type
271         if carrier.type == "application/vnd.wfa.wsc":
272             print "WPS carrier type match - send to wpa_supplicant"
273             if "OK" in wpas_report_handover(data, carrier.record, "INIT"):
274                 success_report("Handover reported successfully (initiator)")
275             else:
276                 summary("Handover report rejected (initiator)")
277             # nfcpy does not support the new format..
278             #wifi = nfc.ndef.WifiConfigRecord(carrier.record)
279             #print wifi.pretty()
280
281     print "Remove peer"
282     client.close()
283     print "Done with handover"
284     global only_one
285     if only_one:
286         global continue_loop
287         continue_loop = False
288
289     global no_wait
290     if no_wait:
291         print "Trying to exit.."
292         global terminate_now
293         terminate_now = True
294
295 def wps_tag_read(tag, wait_remove=True):
296     success = False
297     if len(tag.ndef.message):
298         for record in tag.ndef.message:
299             print "record type " + record.type
300             if record.type == "application/vnd.wfa.wsc":
301                 summary("WPS tag - send to wpa_supplicant")
302                 success = wpas_tag_read(tag.ndef.message)
303                 break
304     else:
305         summary("Empty tag")
306
307     if success:
308         success_report("Tag read succeeded")
309
310     if wait_remove:
311         print "Remove tag"
312         while tag.is_present:
313             time.sleep(0.1)
314
315     return success
316
317
318 def rdwr_connected_write(tag):
319     summary("Tag found - writing - " + str(tag))
320     global write_data
321     tag.ndef.message = str(write_data)
322     success_report("Tag write succeeded")
323     print "Done - remove tag"
324     global only_one
325     if only_one:
326         global continue_loop
327         continue_loop = False
328     global write_wait_remove
329     while write_wait_remove and tag.is_present:
330         time.sleep(0.1)
331
332 def wps_write_config_tag(clf, id=None, wait_remove=True):
333     print "Write WPS config token"
334     global write_data, write_wait_remove
335     write_wait_remove = wait_remove
336     write_data = wpas_get_config_token(id)
337     if write_data == None:
338         print "Could not get WPS config token from wpa_supplicant"
339         sys.exit(1)
340         return
341     print "Touch an NFC tag"
342     clf.connect(rdwr={'on-connect': rdwr_connected_write})
343
344
345 def wps_write_er_config_tag(clf, uuid, wait_remove=True):
346     print "Write WPS ER config token"
347     global write_data, write_wait_remove
348     write_wait_remove = wait_remove
349     write_data = wpas_get_er_config_token(uuid)
350     if write_data == None:
351         print "Could not get WPS config token from wpa_supplicant"
352         return
353
354     print "Touch an NFC tag"
355     clf.connect(rdwr={'on-connect': rdwr_connected_write})
356
357
358 def wps_write_password_tag(clf, wait_remove=True):
359     print "Write WPS password token"
360     global write_data, write_wait_remove
361     write_wait_remove = wait_remove
362     write_data = wpas_get_password_token()
363     if write_data == None:
364         print "Could not get WPS password token from wpa_supplicant"
365         return
366
367     print "Touch an NFC tag"
368     clf.connect(rdwr={'on-connect': rdwr_connected_write})
369
370
371 def rdwr_connected(tag):
372     global only_one, no_wait
373     summary("Tag connected: " + str(tag))
374
375     if tag.ndef:
376         print "NDEF tag: " + tag.type
377         try:
378             print tag.ndef.message.pretty()
379         except Exception, e:
380             print e
381         success = wps_tag_read(tag, not only_one)
382         if only_one and success:
383             global continue_loop
384             continue_loop = False
385     else:
386         summary("Not an NDEF tag - remove tag")
387         return True
388
389     return not no_wait
390
391
392 def llcp_worker(llc):
393     global arg_uuid
394     if arg_uuid is None:
395         wps_handover_init(llc)
396         print "Exiting llcp_worker thread"
397         return
398
399     global srv
400     global wait_connection
401     while not wait_connection and srv.sent_carrier is None:
402         if srv.ho_server_processing:
403             time.sleep(0.025)
404
405 def llcp_startup(clf, llc):
406     global arg_uuid
407     if arg_uuid:
408         print "Start LLCP server"
409         global srv
410         srv = HandoverServer(llc)
411         if arg_uuid is "ap":
412             print "Trying to handle WPS handover"
413             srv.uuid = None
414         else:
415             print "Trying to handle WPS handover with AP " + arg_uuid
416             srv.uuid = arg_uuid
417     return llc
418
419 def llcp_connected(llc):
420     print "P2P LLCP connected"
421     global wait_connection
422     wait_connection = False
423     global arg_uuid
424     if arg_uuid:
425         global srv
426         srv.start()
427     else:
428         threading.Thread(target=llcp_worker, args=(llc,)).start()
429     print "llcp_connected returning"
430     return True
431
432
433 def terminate_loop():
434     global terminate_now
435     return terminate_now
436
437 def main():
438     clf = nfc.ContactlessFrontend()
439
440     parser = argparse.ArgumentParser(description='nfcpy to wpa_supplicant integration for WPS NFC operations')
441     parser.add_argument('-d', const=logging.DEBUG, default=logging.INFO,
442                         action='store_const', dest='loglevel',
443                         help='verbose debug output')
444     parser.add_argument('-q', const=logging.WARNING, action='store_const',
445                         dest='loglevel', help='be quiet')
446     parser.add_argument('--only-one', '-1', action='store_true',
447                         help='run only one operation and exit')
448     parser.add_argument('--no-wait', action='store_true',
449                         help='do not wait for tag to be removed before exiting')
450     parser.add_argument('--uuid',
451                         help='UUID of an AP (used for WPS ER operations)')
452     parser.add_argument('--id',
453                         help='network id (used for WPS ER operations)')
454     parser.add_argument('--summary',
455                         help='summary file for writing status updates')
456     parser.add_argument('--success',
457                         help='success file for writing success update')
458     parser.add_argument('command', choices=['write-config',
459                                             'write-er-config',
460                                             'write-password'],
461                         nargs='?')
462     args = parser.parse_args()
463
464     global arg_uuid
465     arg_uuid = args.uuid
466
467     global only_one
468     only_one = args.only_one
469
470     global no_wait
471     no_wait = args.no_wait
472
473     if args.summary:
474         global summary_file
475         summary_file = args.summary
476
477     if args.success:
478         global success_file
479         success_file = args.success
480
481     logging.basicConfig(level=args.loglevel)
482
483     try:
484         if not clf.open("usb"):
485             print "Could not open connection with an NFC device"
486             raise SystemExit
487
488         if args.command == "write-config":
489             wps_write_config_tag(clf, id=args.id, wait_remove=not args.no_wait)
490             raise SystemExit
491
492         if args.command == "write-er-config":
493             wps_write_er_config_tag(clf, args.uuid, wait_remove=not args.no_wait)
494             raise SystemExit
495
496         if args.command == "write-password":
497             wps_write_password_tag(clf, wait_remove=not args.no_wait)
498             raise SystemExit
499
500         global continue_loop
501         while continue_loop:
502             print "Waiting for a tag or peer to be touched"
503             wait_connection = True
504             try:
505                 if not clf.connect(rdwr={'on-connect': rdwr_connected},
506                                    llcp={'on-startup': llcp_startup,
507                                          'on-connect': llcp_connected},
508                                    terminate=terminate_loop):
509                     break
510             except Exception, e:
511                 print "clf.connect failed"
512
513             global srv
514             if only_one and srv and srv.success:
515                 raise SystemExit
516
517     except KeyboardInterrupt:
518         raise SystemExit
519     finally:
520         clf.close()
521
522     raise SystemExit
523
524 if __name__ == '__main__':
525     main()