1#!/usr/bin/python 2# 3# Example nfcpy to hostapd wrapper for WPS 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 9import os 10import sys 11import time 12import argparse 13 14import nfc 15import nfc.ndef 16import nfc.llcp 17import nfc.handover 18 19import logging 20 21import wpaspy 22 23wpas_ctrl = '/var/run/hostapd' 24continue_loop = True 25summary_file = None 26success_file = None 27 28def summary(txt): 29 print(txt) 30 if summary_file: 31 with open(summary_file, 'a') as f: 32 f.write(txt + "\n") 33 34def success_report(txt): 35 summary(txt) 36 if success_file: 37 with open(success_file, 'a') as f: 38 f.write(txt + "\n") 39 40def 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 as 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 as e: 58 pass 59 return None 60 61 62def 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 71def 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 81def 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 91def 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 101def 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 110class 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 as 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 as 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 173def 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 191def 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 205def 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 218def 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 231def 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 as 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 252def llcp_startup(clf, llc): 253 print("Start LLCP server") 254 global srv 255 srv = HandoverServer(llc) 256 return llc 257 258def 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 267def 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 as 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 341if __name__ == '__main__': 342 main() 343