P2P NFC: Add script for connection handover with nfcpy
[mech_eap.git] / wpa_supplicant / examples / p2p-nfc.py
1 #!/usr/bin/python
2 #
3 # Example nfcpy to wpa_supplicant wrapper for P2P 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 ifname = None
27 init_on_touch = False
28 in_raw_mode = False
29 prev_tcgetattr = 0
30 include_wps_req = True
31 include_p2p_req = True
32 no_input = False
33 srv = None
34 continue_loop = True
35 terminate_now = False
36
37 def wpas_connect():
38     ifaces = []
39     if os.path.isdir(wpas_ctrl):
40         try:
41             ifaces = [os.path.join(wpas_ctrl, i) for i in os.listdir(wpas_ctrl)]
42         except OSError, error:
43             print "Could not find wpa_supplicant: ", error
44             return None
45
46     if len(ifaces) < 1:
47         print "No wpa_supplicant control interface found"
48         return None
49
50     for ctrl in ifaces:
51         if ifname:
52             if ifname not in ctrl:
53                 continue
54         try:
55             print "Trying to use control interface " + ctrl
56             wpas = wpaspy.Ctrl(ctrl)
57             return wpas
58         except Exception, e:
59             pass
60     return None
61
62
63 def wpas_tag_read(message):
64     wpas = wpas_connect()
65     if (wpas == None):
66         return
67     cmd = "WPS_NFC_TAG_READ " + str(message).encode("hex")
68     global force_freq
69     if force_freq:
70         cmd = cmd + " freq=" + force_freq
71     if "FAIL" in wpas.request(cmd):
72         return False
73     return True
74
75
76 def wpas_get_handover_req():
77     wpas = wpas_connect()
78     if (wpas == None):
79         return None
80     res = wpas.request("NFC_GET_HANDOVER_REQ NDEF P2P-CR").rstrip().decode("hex")
81     if "FAIL" in res:
82         return None
83     return res
84
85 def wpas_get_handover_req_wps():
86     wpas = wpas_connect()
87     if (wpas == None):
88         return None
89     return wpas.request("NFC_GET_HANDOVER_REQ NDEF WPS-CR").rstrip().decode("hex")
90
91
92 def wpas_get_handover_sel(tag=False):
93     wpas = wpas_connect()
94     if (wpas == None):
95         return None
96     if tag:
97         return wpas.request("NFC_GET_HANDOVER_SEL NDEF P2P-CR-TAG").rstrip().decode("hex")
98     return wpas.request("NFC_GET_HANDOVER_SEL NDEF P2P-CR").rstrip().decode("hex")
99
100
101 def wpas_get_handover_sel_wps():
102     wpas = wpas_connect()
103     if (wpas == None):
104         return None
105     res = wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR");
106     if "FAIL" in res:
107         return None
108     return res.rstrip().decode("hex")
109
110
111 def wpas_report_handover(req, sel, type):
112     wpas = wpas_connect()
113     if (wpas == None):
114         return None
115     cmd = "NFC_REPORT_HANDOVER " + type + " P2P " + str(req).encode("hex") + " " + str(sel).encode("hex")
116     global force_freq
117     if force_freq:
118         cmd = cmd + " freq=" + force_freq
119     return wpas.request(cmd)
120
121
122 def wpas_report_handover_wsc(req, sel, type):
123     wpas = wpas_connect()
124     if (wpas == None):
125         return None
126     cmd = "NFC_REPORT_HANDOVER " + type + " WPS " + str(req).encode("hex") + " " + str(sel).encode("hex")
127     if force_freq:
128         cmd = cmd + " freq=" + force_freq
129     return wpas.request(cmd)
130
131
132 def p2p_handover_client(llc):
133     message = nfc.ndef.HandoverRequestMessage(version="1.2")
134     message.nonce = random.randint(0, 0xffff)
135
136     global include_p2p_req
137     if include_p2p_req:
138         data = wpas_get_handover_req()
139         if (data == None):
140             print "Could not get handover request carrier record from wpa_supplicant"
141             return
142         print "Handover request carrier record from wpa_supplicant: " + data.encode("hex")
143         datamsg = nfc.ndef.Message(data)
144         message.add_carrier(datamsg[0], "active", datamsg[1:])
145
146     global include_wps_req
147     if include_wps_req:
148         print "Handover request (pre-WPS):"
149         try:
150             print message.pretty()
151         except Exception, e:
152             print e
153
154         data = wpas_get_handover_req_wps()
155         if data:
156             print "Add WPS request in addition to P2P"
157             datamsg = nfc.ndef.Message(data)
158             message.add_carrier(datamsg[0], "active", datamsg[1:])
159
160     print "Handover request:"
161     try:
162         print message.pretty()
163     except Exception, e:
164         print e
165     print str(message).encode("hex")
166
167     client = nfc.handover.HandoverClient(llc)
168     try:
169         print "Trying handover";
170         client.connect()
171         print "Connected for handover"
172     except nfc.llcp.ConnectRefused:
173         print "Handover connection refused"
174         client.close()
175         return
176     except Exception, e:
177         print "Other exception: " + str(e)
178         client.close()
179         return
180
181     print "Sending handover request"
182
183     if not client.send(message):
184         print "Failed to send handover request"
185
186     print "Receiving handover response"
187     message = client._recv()
188     if message is None:
189         print "No response received"
190         client.close()
191         return
192     if message.type != "urn:nfc:wkt:Hs":
193         print "Response was not Hs - received: " + message.type
194         client.close()
195         return
196
197     print "Received message"
198     try:
199         print message.pretty()
200     except Exception, e:
201         print e
202     print str(message).encode("hex")
203     message = nfc.ndef.HandoverSelectMessage(message)
204     print "Handover select received"
205     try:
206         print message.pretty()
207     except Exception, e:
208         print e
209
210     for carrier in message.carriers:
211         print "Remote carrier type: " + carrier.type
212         if carrier.type == "application/vnd.wfa.p2p":
213             print "P2P carrier type match - send to wpa_supplicant"
214             wpas_report_handover(data, carrier.record, "INIT")
215             break
216
217     print "Remove peer"
218     client.close()
219     print "Done with handover"
220     global only_one
221     if only_one:
222         print "only_one -> stop loop"
223         global continue_loop
224         continue_loop = False
225
226     global no_wait
227     if no_wait:
228         print "Trying to exit.."
229         global terminate_now
230         terminate_now = True
231
232
233 class HandoverServer(nfc.handover.HandoverServer):
234     def __init__(self, llc):
235         super(HandoverServer, self).__init__(llc)
236         self.sent_carrier = None
237         self.ho_server_processing = False
238         self.success = False
239
240     def process_request(self, request):
241         self.ho_server_processing = True
242         clear_raw_mode()
243         print "HandoverServer - request received"
244         try:
245             print "Parsed handover request: " + request.pretty()
246         except Exception, e:
247             print e
248
249         sel = nfc.ndef.HandoverSelectMessage(version="1.2")
250
251         found = False
252
253         for carrier in request.carriers:
254             print "Remote carrier type: " + carrier.type
255             if carrier.type == "application/vnd.wfa.p2p":
256                 print "P2P carrier type match - add P2P carrier record"
257                 found = True
258                 self.received_carrier = carrier.record
259                 print "Carrier record:"
260                 try:
261                     print carrier.record.pretty()
262                 except Exception, e:
263                     print e
264                 data = wpas_get_handover_sel()
265                 if data is None:
266                     print "Could not get handover select carrier record from wpa_supplicant"
267                     continue
268                 print "Handover select carrier record from wpa_supplicant:"
269                 print data.encode("hex")
270                 self.sent_carrier = data
271                 wpas_report_handover(self.received_carrier, self.sent_carrier,
272                                      "RESP")
273
274                 message = nfc.ndef.Message(data);
275                 sel.add_carrier(message[0], "active", message[1:])
276                 break
277
278         for carrier in request.carriers:
279             if found:
280                 break
281             print "Remote carrier type: " + carrier.type
282             if carrier.type == "application/vnd.wfa.wsc":
283                 print "WSC carrier type match - add WSC carrier record"
284                 found = True
285                 self.received_carrier = carrier.record
286                 print "Carrier record:"
287                 try:
288                     print carrier.record.pretty()
289                 except Exception, e:
290                     print e
291                 data = wpas_get_handover_sel_wps()
292                 if data is None:
293                     print "Could not get handover select carrier record from wpa_supplicant"
294                     continue
295                 print "Handover select carrier record from wpa_supplicant:"
296                 print data.encode("hex")
297                 self.sent_carrier = data
298                 wpas_report_handover_wsc(self.received_carrier,
299                                          self.sent_carrier, "RESP")
300
301                 message = nfc.ndef.Message(data);
302                 sel.add_carrier(message[0], "active", message[1:])
303                 found = True
304                 break
305
306         print "Handover select:"
307         try:
308             print sel.pretty()
309         except Exception, e:
310             print e
311         print str(sel).encode("hex")
312
313         print "Sending handover select"
314         self.success = True
315         return sel
316
317
318 def clear_raw_mode():
319     import sys, tty, termios
320     global prev_tcgetattr, in_raw_mode
321     if not in_raw_mode:
322         return
323     fd = sys.stdin.fileno()
324     termios.tcsetattr(fd, termios.TCSADRAIN, prev_tcgetattr)
325     in_raw_mode = False
326
327
328 def getch():
329     import sys, tty, termios, select
330     global prev_tcgetattr, in_raw_mode
331     fd = sys.stdin.fileno()
332     prev_tcgetattr = termios.tcgetattr(fd)
333     ch = None
334     try:
335         tty.setraw(fd)
336         in_raw_mode = True
337         [i, o, e] = select.select([fd], [], [], 0.05)
338         if i:
339             ch = sys.stdin.read(1)
340     finally:
341         termios.tcsetattr(fd, termios.TCSADRAIN, prev_tcgetattr)
342         in_raw_mode = False
343     return ch
344
345
346 def p2p_tag_read(tag):
347     success = False
348     if len(tag.ndef.message):
349         for record in tag.ndef.message:
350             print "record type " + record.type
351             if record.type == "application/vnd.wfa.wsc":
352                 print "WPS tag - send to wpa_supplicant"
353                 success = wpas_tag_read(tag.ndef.message)
354                 break
355             if record.type == "application/vnd.wfa.p2p":
356                 print "P2P tag - send to wpa_supplicant"
357                 success = wpas_tag_read(tag.ndef.message)
358                 break
359     else:
360         print "Empty tag"
361
362     return success
363
364
365 def rdwr_connected_p2p_write(tag):
366     print "Tag found - writing"
367     global p2p_sel_data
368     tag.ndef.message = str(p2p_sel_data)
369     print "Done - remove tag"
370     global only_one
371     if only_one:
372         global continue_loop
373         continue_loop = False
374     global p2p_sel_wait_remove
375     return p2p_sel_wait_remove
376
377 def wps_write_p2p_handover_sel(clf, wait_remove=True):
378     print "Write P2P handover select"
379     data = wpas_get_handover_sel(tag=True)
380     if (data == None):
381         print "Could not get P2P handover select from wpa_supplicant"
382         return
383
384     global p2p_sel_wait_remove
385     p2p_sel_wait_remove = wait_remove
386     global p2p_sel_data
387     p2p_sel_data = nfc.ndef.HandoverSelectMessage(version="1.2")
388     message = nfc.ndef.Message(data);
389     p2p_sel_data.add_carrier(message[0], "active", message[1:])
390     print "Handover select:"
391     try:
392         print p2p_sel_data.pretty()
393     except Exception, e:
394         print e
395     print str(p2p_sel_data).encode("hex")
396
397     print "Touch an NFC tag"
398     clf.connect(rdwr={'on-connect': rdwr_connected_p2p_write})
399
400
401 def rdwr_connected(tag):
402     global only_one, no_wait
403     print "Tag connected: " + str(tag)
404
405     if tag.ndef:
406         print "NDEF tag: " + tag.type
407         try:
408             print tag.ndef.message.pretty()
409         except Exception, e:
410             print e
411         success = p2p_tag_read(tag)
412         if only_one and success:
413             global continue_loop
414             continue_loop = False
415     else:
416         print "Not an NDEF tag - remove tag"
417
418     return not no_wait
419
420
421 def llcp_worker(llc):
422     global init_on_touch
423     if init_on_touch:
424             print "Starting handover client"
425             p2p_handover_client(llc)
426             return
427
428     global no_input
429     if no_input:
430         print "Wait for handover to complete"
431     else:
432         print "Wait for handover to complete - press 'i' to initiate ('w' for WPS only, 'p' for P2P only)"
433     global srv
434     global wait_connection
435     while not wait_connection and srv.sent_carrier is None:
436         if srv.ho_server_processing:
437             time.sleep(0.025)
438         elif no_input:
439             time.sleep(0.5)
440         else:
441             global include_wps_req, include_p2p_req
442             res = getch()
443             if res == 'i':
444                 include_wps_req = True
445                 include_p2p_req = True
446             elif res == 'p':
447                 include_wps_req = False
448                 include_p2p_req = True
449             elif res == 'w':
450                 include_wps_req = True
451                 include_p2p_req = False
452             else:
453                 continue
454             clear_raw_mode()
455             print "Starting handover client"
456             p2p_handover_client(llc)
457             return
458             
459     clear_raw_mode()
460     print "Exiting llcp_worker thread"
461
462 def llcp_startup(clf, llc):
463     print "Start LLCP server"
464     global srv
465     srv = HandoverServer(llc)
466     return llc
467
468 def llcp_connected(llc):
469     print "P2P LLCP connected"
470     global wait_connection
471     wait_connection = False
472     global init_on_touch
473     if not init_on_touch:
474         global srv
475         srv.start()
476     if init_on_touch or not no_input:
477         threading.Thread(target=llcp_worker, args=(llc,)).start()
478     return True
479
480 def terminate_loop():
481     global terminate_now
482     return terminate_now
483
484 def main():
485     clf = nfc.ContactlessFrontend()
486
487     parser = argparse.ArgumentParser(description='nfcpy to wpa_supplicant integration for P2P and WPS NFC operations')
488     parser.add_argument('-d', const=logging.DEBUG, default=logging.INFO,
489                         action='store_const', dest='loglevel',
490                         help='verbose debug output')
491     parser.add_argument('-q', const=logging.WARNING, action='store_const',
492                         dest='loglevel', help='be quiet')
493     parser.add_argument('--only-one', '-1', action='store_true',
494                         help='run only one operation and exit')
495     parser.add_argument('--init-on-touch', '-I', action='store_true',
496                         help='initiate handover on touch')
497     parser.add_argument('--no-wait', action='store_true',
498                         help='do not wait for tag to be removed before exiting')
499     parser.add_argument('--ifname', '-i',
500                         help='network interface name')
501     parser.add_argument('--no-wps-req', '-N', action='store_true',
502                         help='do not include WPS carrier record in request')
503     parser.add_argument('--no-input', '-a', action='store_true',
504                         help='do not use stdout input to initiate handover')
505     parser.add_argument('--tag-read-only', '-t', action='store_true',
506                         help='tag read only (do not allow connection handover)')
507     parser.add_argument('--freq', '-f',
508                         help='forced frequency of operating channel in MHz')
509     parser.add_argument('command', choices=['write-p2p-sel'],
510                         nargs='?')
511     args = parser.parse_args()
512
513     global only_one
514     only_one = args.only_one
515
516     global no_wait
517     no_wait = args.no_wait
518
519     global force_freq
520     force_freq = args.freq
521
522     logging.basicConfig(level=args.loglevel)
523
524     global init_on_touch
525     init_on_touch = args.init_on_touch
526
527     if args.ifname:
528         global ifname
529         ifname = args.ifname
530         print "Selected ifname " + ifname
531
532     if args.no_wps_req:
533         global include_wps_req
534         include_wps_req = False
535
536     if args.no_input:
537         global no_input
538         no_input = True
539
540     clf = nfc.ContactlessFrontend()
541     global wait_connection
542
543     try:
544         if not clf.open("usb"):
545             print "Could not open connection with an NFC device"
546             raise SystemExit
547
548         if args.command == "write-p2p-sel":
549             wps_write_p2p_handover_sel(clf, wait_remove=not args.no_wait)
550             raise SystemExit
551
552         global continue_loop
553         while continue_loop:
554             print "Waiting for a tag or peer to be touched"
555             wait_connection = True
556             try:
557                 if args.tag_read_only:
558                     if not clf.connect(rdwr={'on-connect': rdwr_connected}):
559                         break
560                 else:
561                     if not clf.connect(rdwr={'on-connect': rdwr_connected},
562                                        llcp={'on-startup': llcp_startup,
563                                              'on-connect': llcp_connected},
564                                        terminate=terminate_loop):
565                         break
566             except Exception, e:
567                 print "clf.connect failed"
568
569             global srv
570             if only_one and srv and srv.success:
571                 raise SystemExit
572
573     except KeyboardInterrupt:
574         raise SystemExit
575     finally:
576         clf.close()
577
578     raise SystemExit
579
580 if __name__ == '__main__':
581     main()