Home | History | Annotate | Download | only in examples
      1 #!/usr/bin/python
      2 #
      3 # Example nfcpy to wpa_supplicant wrapper for WPS 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 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()
    526