1 #!/usr/bin/python 2 # 3 # Example nfcpy to wpa_supplicant wrapper for P2P NFC operations 4 # Copyright (c) 2012-2013, Jouni Malinen <j (at] 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() 655