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