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