Home | History | Annotate | Download | only in hostapd
      1 #!/usr/bin/python
      2 #
      3 # Example nfcpy to hostapd 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 argparse
     13 
     14 import nfc
     15 import nfc.ndef
     16 import nfc.llcp
     17 import nfc.handover
     18 
     19 import logging
     20 
     21 import wpaspy
     22 
     23 wpas_ctrl = '/var/run/hostapd'
     24 continue_loop = True
     25 summary_file = None
     26 success_file = None
     27 
     28 def summary(txt):
     29     print txt
     30     if summary_file:
     31         with open(summary_file, 'a') as f:
     32             f.write(txt + "\n")
     33 
     34 def success_report(txt):
     35     summary(txt)
     36     if success_file:
     37         with open(success_file, 'a') as f:
     38             f.write(txt + "\n")
     39 
     40 def wpas_connect():
     41     ifaces = []
     42     if os.path.isdir(wpas_ctrl):
     43         try:
     44             ifaces = [os.path.join(wpas_ctrl, i) for i in os.listdir(wpas_ctrl)]
     45         except OSError, error:
     46             print "Could not find hostapd: ", error
     47             return None
     48 
     49     if len(ifaces) < 1:
     50         print "No hostapd control interface found"
     51         return None
     52 
     53     for ctrl in ifaces:
     54         try:
     55             wpas = wpaspy.Ctrl(ctrl)
     56             return wpas
     57         except Exception, e:
     58             pass
     59     return None
     60 
     61 
     62 def wpas_tag_read(message):
     63     wpas = wpas_connect()
     64     if (wpas == None):
     65         return False
     66     if "FAIL" in wpas.request("WPS_NFC_TAG_READ " + str(message).encode("hex")):
     67         return False
     68     return True
     69 
     70 
     71 def wpas_get_config_token():
     72     wpas = wpas_connect()
     73     if (wpas == None):
     74         return None
     75     ret = wpas.request("WPS_NFC_CONFIG_TOKEN NDEF")
     76     if "FAIL" in ret:
     77         return None
     78     return ret.rstrip().decode("hex")
     79 
     80 
     81 def wpas_get_password_token():
     82     wpas = wpas_connect()
     83     if (wpas == None):
     84         return None
     85     ret = wpas.request("WPS_NFC_TOKEN NDEF")
     86     if "FAIL" in ret:
     87         return None
     88     return ret.rstrip().decode("hex")
     89 
     90 
     91 def wpas_get_handover_sel():
     92     wpas = wpas_connect()
     93     if (wpas == None):
     94         return None
     95     ret = wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR")
     96     if "FAIL" in ret:
     97         return None
     98     return ret.rstrip().decode("hex")
     99 
    100 
    101 def wpas_report_handover(req, sel):
    102     wpas = wpas_connect()
    103     if (wpas == None):
    104         return None
    105     return wpas.request("NFC_REPORT_HANDOVER RESP WPS " +
    106                         str(req).encode("hex") + " " +
    107                         str(sel).encode("hex"))
    108 
    109 
    110 class HandoverServer(nfc.handover.HandoverServer):
    111     def __init__(self, llc):
    112         super(HandoverServer, self).__init__(llc)
    113         self.ho_server_processing = False
    114         self.success = False
    115 
    116     # override to avoid parser error in request/response.pretty() in nfcpy
    117     # due to new WSC handover format
    118     def _process_request(self, request):
    119         summary("received handover request {}".format(request.type))
    120         response = nfc.ndef.Message("\xd1\x02\x01Hs\x12")
    121         if not request.type == 'urn:nfc:wkt:Hr':
    122             summary("not a handover request")
    123         else:
    124             try:
    125                 request = nfc.ndef.HandoverRequestMessage(request)
    126             except nfc.ndef.DecodeError as e:
    127                 summary("error decoding 'Hr' message: {}".format(e))
    128             else:
    129                 response = self.process_request(request)
    130         summary("send handover response {}".format(response.type))
    131         return response
    132 
    133     def process_request(self, request):
    134         summary("HandoverServer - request received")
    135         try:
    136             print "Parsed handover request: " + request.pretty()
    137         except Exception, e:
    138             print e
    139         print str(request).encode("hex")
    140 
    141         sel = nfc.ndef.HandoverSelectMessage(version="1.2")
    142 
    143         for carrier in request.carriers:
    144             print "Remote carrier type: " + carrier.type
    145             if carrier.type == "application/vnd.wfa.wsc":
    146                 summary("WPS carrier type match - add WPS carrier record")
    147                 data = wpas_get_handover_sel()
    148                 if data is None:
    149                     summary("Could not get handover select carrier record from hostapd")
    150                     continue
    151                 print "Handover select carrier record from hostapd:"
    152                 print data.encode("hex")
    153                 if "OK" in wpas_report_handover(carrier.record, data):
    154                     success_report("Handover reported successfully")
    155                 else:
    156                     summary("Handover report rejected")
    157 
    158                 message = nfc.ndef.Message(data);
    159                 sel.add_carrier(message[0], "active", message[1:])
    160 
    161         print "Handover select:"
    162         try:
    163             print sel.pretty()
    164         except Exception, e:
    165             print e
    166         print str(sel).encode("hex")
    167 
    168         summary("Sending handover select")
    169         self.success = True
    170         return sel
    171 
    172 
    173 def wps_tag_read(tag):
    174     success = False
    175     if len(tag.ndef.message):
    176         for record in tag.ndef.message:
    177             print "record type " + record.type
    178             if record.type == "application/vnd.wfa.wsc":
    179                 summary("WPS tag - send to hostapd")
    180                 success = wpas_tag_read(tag.ndef.message)
    181                 break
    182     else:
    183         summary("Empty tag")
    184 
    185     if success:
    186         success_report("Tag read succeeded")
    187 
    188     return success
    189 
    190 
    191 def rdwr_connected_write(tag):
    192     summary("Tag found - writing - " + str(tag))
    193     global write_data
    194     tag.ndef.message = str(write_data)
    195     success_report("Tag write succeeded")
    196     print "Done - remove tag"
    197     global only_one
    198     if only_one:
    199         global continue_loop
    200         continue_loop = False
    201     global write_wait_remove
    202     while write_wait_remove and tag.is_present:
    203         time.sleep(0.1)
    204 
    205 def wps_write_config_tag(clf, wait_remove=True):
    206     summary("Write WPS config token")
    207     global write_data, write_wait_remove
    208     write_wait_remove = wait_remove
    209     write_data = wpas_get_config_token()
    210     if write_data == None:
    211         summary("Could not get WPS config token from hostapd")
    212         return
    213 
    214     print "Touch an NFC tag"
    215     clf.connect(rdwr={'on-connect': rdwr_connected_write})
    216 
    217 
    218 def wps_write_password_tag(clf, wait_remove=True):
    219     summary("Write WPS password token")
    220     global write_data, write_wait_remove
    221     write_wait_remove = wait_remove
    222     write_data = wpas_get_password_token()
    223     if write_data == None:
    224         summary("Could not get WPS password token from hostapd")
    225         return
    226 
    227     print "Touch an NFC tag"
    228     clf.connect(rdwr={'on-connect': rdwr_connected_write})
    229 
    230 
    231 def rdwr_connected(tag):
    232     global only_one, no_wait
    233     summary("Tag connected: " + str(tag))
    234 
    235     if tag.ndef:
    236         print "NDEF tag: " + tag.type
    237         try:
    238             print tag.ndef.message.pretty()
    239         except Exception, e:
    240             print e
    241         success = wps_tag_read(tag)
    242         if only_one and success:
    243             global continue_loop
    244             continue_loop = False
    245     else:
    246         summary("Not an NDEF tag - remove tag")
    247         return True
    248 
    249     return not no_wait
    250 
    251 
    252 def llcp_startup(clf, llc):
    253     print "Start LLCP server"
    254     global srv
    255     srv = HandoverServer(llc)
    256     return llc
    257 
    258 def llcp_connected(llc):
    259     print "P2P LLCP connected"
    260     global wait_connection
    261     wait_connection = False
    262     global srv
    263     srv.start()
    264     return True
    265 
    266 
    267 def main():
    268     clf = nfc.ContactlessFrontend()
    269 
    270     parser = argparse.ArgumentParser(description='nfcpy to hostapd integration for WPS NFC operations')
    271     parser.add_argument('-d', const=logging.DEBUG, default=logging.INFO,
    272                         action='store_const', dest='loglevel',
    273                         help='verbose debug output')
    274     parser.add_argument('-q', const=logging.WARNING, action='store_const',
    275                         dest='loglevel', help='be quiet')
    276     parser.add_argument('--only-one', '-1', action='store_true',
    277                         help='run only one operation and exit')
    278     parser.add_argument('--no-wait', action='store_true',
    279                         help='do not wait for tag to be removed before exiting')
    280     parser.add_argument('--summary',
    281                         help='summary file for writing status updates')
    282     parser.add_argument('--success',
    283                         help='success file for writing success update')
    284     parser.add_argument('command', choices=['write-config',
    285                                             'write-password'],
    286                         nargs='?')
    287     args = parser.parse_args()
    288 
    289     global only_one
    290     only_one = args.only_one
    291 
    292     global no_wait
    293     no_wait = args.no_wait
    294 
    295     if args.summary:
    296         global summary_file
    297         summary_file = args.summary
    298 
    299     if args.success:
    300         global success_file
    301         success_file = args.success
    302 
    303     logging.basicConfig(level=args.loglevel)
    304 
    305     try:
    306         if not clf.open("usb"):
    307             print "Could not open connection with an NFC device"
    308             raise SystemExit
    309 
    310         if args.command == "write-config":
    311             wps_write_config_tag(clf, wait_remove=not args.no_wait)
    312             raise SystemExit
    313 
    314         if args.command == "write-password":
    315             wps_write_password_tag(clf, wait_remove=not args.no_wait)
    316             raise SystemExit
    317 
    318         global continue_loop
    319         while continue_loop:
    320             print "Waiting for a tag or peer to be touched"
    321             wait_connection = True
    322             try:
    323                 if not clf.connect(rdwr={'on-connect': rdwr_connected},
    324                                    llcp={'on-startup': llcp_startup,
    325                                          'on-connect': llcp_connected}):
    326                     break
    327             except Exception, e:
    328                 print "clf.connect failed"
    329 
    330             global srv
    331             if only_one and srv and srv.success:
    332                 raise SystemExit
    333 
    334     except KeyboardInterrupt:
    335         raise SystemExit
    336     finally:
    337         clf.close()
    338 
    339     raise SystemExit
    340 
    341 if __name__ == '__main__':
    342     main()
    343