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