1281681Srpaulo#!/usr/bin/python
2281681Srpaulo#
3281681Srpaulo# Example nfcpy to hostapd wrapper for WPS NFC operations
4281681Srpaulo# Copyright (c) 2012-2013, Jouni Malinen <j@w1.fi>
5281681Srpaulo#
6281681Srpaulo# This software may be distributed under the terms of the BSD license.
7281681Srpaulo# See README for more details.
8281681Srpaulo
9281681Srpauloimport os
10281681Srpauloimport sys
11281681Srpauloimport time
12281681Srpauloimport argparse
13281681Srpaulo
14281681Srpauloimport nfc
15281681Srpauloimport nfc.ndef
16281681Srpauloimport nfc.llcp
17281681Srpauloimport nfc.handover
18281681Srpaulo
19281681Srpauloimport logging
20281681Srpaulo
21281681Srpauloimport wpaspy
22281681Srpaulo
23281681Srpaulowpas_ctrl = '/var/run/hostapd'
24281681Srpaulocontinue_loop = True
25281681Srpaulosummary_file = None
26281681Srpaulosuccess_file = None
27281681Srpaulo
28281681Srpaulodef summary(txt):
29281681Srpaulo    print txt
30281681Srpaulo    if summary_file:
31281681Srpaulo        with open(summary_file, 'a') as f:
32281681Srpaulo            f.write(txt + "\n")
33281681Srpaulo
34281681Srpaulodef success_report(txt):
35281681Srpaulo    summary(txt)
36281681Srpaulo    if success_file:
37281681Srpaulo        with open(success_file, 'a') as f:
38281681Srpaulo            f.write(txt + "\n")
39281681Srpaulo
40281681Srpaulodef wpas_connect():
41281681Srpaulo    ifaces = []
42281681Srpaulo    if os.path.isdir(wpas_ctrl):
43281681Srpaulo        try:
44281681Srpaulo            ifaces = [os.path.join(wpas_ctrl, i) for i in os.listdir(wpas_ctrl)]
45281681Srpaulo        except OSError, error:
46281681Srpaulo            print "Could not find hostapd: ", error
47281681Srpaulo            return None
48281681Srpaulo
49281681Srpaulo    if len(ifaces) < 1:
50281681Srpaulo        print "No hostapd control interface found"
51281681Srpaulo        return None
52281681Srpaulo
53281681Srpaulo    for ctrl in ifaces:
54281681Srpaulo        try:
55281681Srpaulo            wpas = wpaspy.Ctrl(ctrl)
56281681Srpaulo            return wpas
57281681Srpaulo        except Exception, e:
58281681Srpaulo            pass
59281681Srpaulo    return None
60281681Srpaulo
61281681Srpaulo
62281681Srpaulodef wpas_tag_read(message):
63281681Srpaulo    wpas = wpas_connect()
64281681Srpaulo    if (wpas == None):
65281681Srpaulo        return False
66281681Srpaulo    if "FAIL" in wpas.request("WPS_NFC_TAG_READ " + str(message).encode("hex")):
67281681Srpaulo        return False
68281681Srpaulo    return True
69281681Srpaulo
70281681Srpaulo
71281681Srpaulodef wpas_get_config_token():
72281681Srpaulo    wpas = wpas_connect()
73281681Srpaulo    if (wpas == None):
74281681Srpaulo        return None
75281681Srpaulo    ret = wpas.request("WPS_NFC_CONFIG_TOKEN NDEF")
76281681Srpaulo    if "FAIL" in ret:
77281681Srpaulo        return None
78281681Srpaulo    return ret.rstrip().decode("hex")
79281681Srpaulo
80281681Srpaulo
81281681Srpaulodef wpas_get_password_token():
82281681Srpaulo    wpas = wpas_connect()
83281681Srpaulo    if (wpas == None):
84281681Srpaulo        return None
85281681Srpaulo    ret = wpas.request("WPS_NFC_TOKEN NDEF")
86281681Srpaulo    if "FAIL" in ret:
87281681Srpaulo        return None
88281681Srpaulo    return ret.rstrip().decode("hex")
89281681Srpaulo
90281681Srpaulo
91281681Srpaulodef wpas_get_handover_sel():
92281681Srpaulo    wpas = wpas_connect()
93281681Srpaulo    if (wpas == None):
94281681Srpaulo        return None
95281681Srpaulo    ret = wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR")
96281681Srpaulo    if "FAIL" in ret:
97281681Srpaulo        return None
98281681Srpaulo    return ret.rstrip().decode("hex")
99281681Srpaulo
100281681Srpaulo
101281681Srpaulodef wpas_report_handover(req, sel):
102281681Srpaulo    wpas = wpas_connect()
103281681Srpaulo    if (wpas == None):
104281681Srpaulo        return None
105281681Srpaulo    return wpas.request("NFC_REPORT_HANDOVER RESP WPS " +
106281681Srpaulo                        str(req).encode("hex") + " " +
107281681Srpaulo                        str(sel).encode("hex"))
108281681Srpaulo
109281681Srpaulo
110281681Srpauloclass HandoverServer(nfc.handover.HandoverServer):
111281681Srpaulo    def __init__(self, llc):
112281681Srpaulo        super(HandoverServer, self).__init__(llc)
113281681Srpaulo        self.ho_server_processing = False
114281681Srpaulo        self.success = False
115281681Srpaulo
116281681Srpaulo    # override to avoid parser error in request/response.pretty() in nfcpy
117281681Srpaulo    # due to new WSC handover format
118281681Srpaulo    def _process_request(self, request):
119281681Srpaulo        summary("received handover request {}".format(request.type))
120281681Srpaulo        response = nfc.ndef.Message("\xd1\x02\x01Hs\x12")
121281681Srpaulo        if not request.type == 'urn:nfc:wkt:Hr':
122281681Srpaulo            summary("not a handover request")
123281681Srpaulo        else:
124281681Srpaulo            try:
125281681Srpaulo                request = nfc.ndef.HandoverRequestMessage(request)
126281681Srpaulo            except nfc.ndef.DecodeError as e:
127281681Srpaulo                summary("error decoding 'Hr' message: {}".format(e))
128281681Srpaulo            else:
129281681Srpaulo                response = self.process_request(request)
130281681Srpaulo        summary("send handover response {}".format(response.type))
131281681Srpaulo        return response
132281681Srpaulo
133281681Srpaulo    def process_request(self, request):
134281681Srpaulo        summary("HandoverServer - request received")
135281681Srpaulo        try:
136281681Srpaulo            print "Parsed handover request: " + request.pretty()
137281681Srpaulo        except Exception, e:
138281681Srpaulo            print e
139281681Srpaulo        print str(request).encode("hex")
140281681Srpaulo
141281681Srpaulo        sel = nfc.ndef.HandoverSelectMessage(version="1.2")
142281681Srpaulo
143281681Srpaulo        for carrier in request.carriers:
144281681Srpaulo            print "Remote carrier type: " + carrier.type
145281681Srpaulo            if carrier.type == "application/vnd.wfa.wsc":
146281681Srpaulo                summary("WPS carrier type match - add WPS carrier record")
147281681Srpaulo                data = wpas_get_handover_sel()
148281681Srpaulo                if data is None:
149281681Srpaulo                    summary("Could not get handover select carrier record from hostapd")
150281681Srpaulo                    continue
151281681Srpaulo                print "Handover select carrier record from hostapd:"
152281681Srpaulo                print data.encode("hex")
153281681Srpaulo                if "OK" in wpas_report_handover(carrier.record, data):
154281681Srpaulo                    success_report("Handover reported successfully")
155281681Srpaulo                else:
156281681Srpaulo                    summary("Handover report rejected")
157281681Srpaulo
158281681Srpaulo                message = nfc.ndef.Message(data);
159281681Srpaulo                sel.add_carrier(message[0], "active", message[1:])
160281681Srpaulo
161281681Srpaulo        print "Handover select:"
162281681Srpaulo        try:
163281681Srpaulo            print sel.pretty()
164281681Srpaulo        except Exception, e:
165281681Srpaulo            print e
166281681Srpaulo        print str(sel).encode("hex")
167281681Srpaulo
168281681Srpaulo        summary("Sending handover select")
169281681Srpaulo        self.success = True
170281681Srpaulo        return sel
171281681Srpaulo
172281681Srpaulo
173281681Srpaulodef wps_tag_read(tag):
174281681Srpaulo    success = False
175281681Srpaulo    if len(tag.ndef.message):
176281681Srpaulo        for record in tag.ndef.message:
177281681Srpaulo            print "record type " + record.type
178281681Srpaulo            if record.type == "application/vnd.wfa.wsc":
179281681Srpaulo                summary("WPS tag - send to hostapd")
180281681Srpaulo                success = wpas_tag_read(tag.ndef.message)
181281681Srpaulo                break
182281681Srpaulo    else:
183281681Srpaulo        summary("Empty tag")
184281681Srpaulo
185281681Srpaulo    if success:
186281681Srpaulo        success_report("Tag read succeeded")
187281681Srpaulo
188281681Srpaulo    return success
189281681Srpaulo
190281681Srpaulo
191281681Srpaulodef rdwr_connected_write(tag):
192281681Srpaulo    summary("Tag found - writing - " + str(tag))
193281681Srpaulo    global write_data
194281681Srpaulo    tag.ndef.message = str(write_data)
195281681Srpaulo    success_report("Tag write succeeded")
196281681Srpaulo    print "Done - remove tag"
197281681Srpaulo    global only_one
198281681Srpaulo    if only_one:
199281681Srpaulo        global continue_loop
200281681Srpaulo        continue_loop = False
201281681Srpaulo    global write_wait_remove
202281681Srpaulo    while write_wait_remove and tag.is_present:
203281681Srpaulo        time.sleep(0.1)
204281681Srpaulo
205281681Srpaulodef wps_write_config_tag(clf, wait_remove=True):
206281681Srpaulo    summary("Write WPS config token")
207281681Srpaulo    global write_data, write_wait_remove
208281681Srpaulo    write_wait_remove = wait_remove
209281681Srpaulo    write_data = wpas_get_config_token()
210281681Srpaulo    if write_data == None:
211281681Srpaulo        summary("Could not get WPS config token from hostapd")
212281681Srpaulo        return
213281681Srpaulo
214281681Srpaulo    print "Touch an NFC tag"
215281681Srpaulo    clf.connect(rdwr={'on-connect': rdwr_connected_write})
216281681Srpaulo
217281681Srpaulo
218281681Srpaulodef wps_write_password_tag(clf, wait_remove=True):
219281681Srpaulo    summary("Write WPS password token")
220281681Srpaulo    global write_data, write_wait_remove
221281681Srpaulo    write_wait_remove = wait_remove
222281681Srpaulo    write_data = wpas_get_password_token()
223281681Srpaulo    if write_data == None:
224281681Srpaulo        summary("Could not get WPS password token from hostapd")
225281681Srpaulo        return
226281681Srpaulo
227281681Srpaulo    print "Touch an NFC tag"
228281681Srpaulo    clf.connect(rdwr={'on-connect': rdwr_connected_write})
229281681Srpaulo
230281681Srpaulo
231281681Srpaulodef rdwr_connected(tag):
232281681Srpaulo    global only_one, no_wait
233281681Srpaulo    summary("Tag connected: " + str(tag))
234281681Srpaulo
235281681Srpaulo    if tag.ndef:
236281681Srpaulo        print "NDEF tag: " + tag.type
237281681Srpaulo        try:
238281681Srpaulo            print tag.ndef.message.pretty()
239281681Srpaulo        except Exception, e:
240281681Srpaulo            print e
241281681Srpaulo        success = wps_tag_read(tag)
242281681Srpaulo        if only_one and success:
243281681Srpaulo            global continue_loop
244281681Srpaulo            continue_loop = False
245281681Srpaulo    else:
246281681Srpaulo        summary("Not an NDEF tag - remove tag")
247281681Srpaulo        return True
248281681Srpaulo
249281681Srpaulo    return not no_wait
250281681Srpaulo
251281681Srpaulo
252281681Srpaulodef llcp_startup(clf, llc):
253281681Srpaulo    print "Start LLCP server"
254281681Srpaulo    global srv
255281681Srpaulo    srv = HandoverServer(llc)
256281681Srpaulo    return llc
257281681Srpaulo
258281681Srpaulodef llcp_connected(llc):
259281681Srpaulo    print "P2P LLCP connected"
260281681Srpaulo    global wait_connection
261281681Srpaulo    wait_connection = False
262281681Srpaulo    global srv
263281681Srpaulo    srv.start()
264281681Srpaulo    return True
265281681Srpaulo
266281681Srpaulo
267281681Srpaulodef main():
268281681Srpaulo    clf = nfc.ContactlessFrontend()
269281681Srpaulo
270281681Srpaulo    parser = argparse.ArgumentParser(description='nfcpy to hostapd integration for WPS NFC operations')
271281681Srpaulo    parser.add_argument('-d', const=logging.DEBUG, default=logging.INFO,
272281681Srpaulo                        action='store_const', dest='loglevel',
273281681Srpaulo                        help='verbose debug output')
274281681Srpaulo    parser.add_argument('-q', const=logging.WARNING, action='store_const',
275281681Srpaulo                        dest='loglevel', help='be quiet')
276281681Srpaulo    parser.add_argument('--only-one', '-1', action='store_true',
277281681Srpaulo                        help='run only one operation and exit')
278281681Srpaulo    parser.add_argument('--no-wait', action='store_true',
279281681Srpaulo                        help='do not wait for tag to be removed before exiting')
280281681Srpaulo    parser.add_argument('--summary',
281281681Srpaulo                        help='summary file for writing status updates')
282281681Srpaulo    parser.add_argument('--success',
283281681Srpaulo                        help='success file for writing success update')
284281681Srpaulo    parser.add_argument('command', choices=['write-config',
285281681Srpaulo                                            'write-password'],
286281681Srpaulo                        nargs='?')
287281681Srpaulo    args = parser.parse_args()
288281681Srpaulo
289281681Srpaulo    global only_one
290281681Srpaulo    only_one = args.only_one
291281681Srpaulo
292281681Srpaulo    global no_wait
293281681Srpaulo    no_wait = args.no_wait
294281681Srpaulo
295281681Srpaulo    if args.summary:
296281681Srpaulo        global summary_file
297281681Srpaulo        summary_file = args.summary
298281681Srpaulo
299281681Srpaulo    if args.success:
300281681Srpaulo        global success_file
301281681Srpaulo        success_file = args.success
302281681Srpaulo
303281681Srpaulo    logging.basicConfig(level=args.loglevel)
304281681Srpaulo
305281681Srpaulo    try:
306281681Srpaulo        if not clf.open("usb"):
307281681Srpaulo            print "Could not open connection with an NFC device"
308281681Srpaulo            raise SystemExit
309281681Srpaulo
310281681Srpaulo        if args.command == "write-config":
311281681Srpaulo            wps_write_config_tag(clf, wait_remove=not args.no_wait)
312281681Srpaulo            raise SystemExit
313281681Srpaulo
314281681Srpaulo        if args.command == "write-password":
315281681Srpaulo            wps_write_password_tag(clf, wait_remove=not args.no_wait)
316281681Srpaulo            raise SystemExit
317281681Srpaulo
318281681Srpaulo        global continue_loop
319281681Srpaulo        while continue_loop:
320281681Srpaulo            print "Waiting for a tag or peer to be touched"
321281681Srpaulo            wait_connection = True
322281681Srpaulo            try:
323281681Srpaulo                if not clf.connect(rdwr={'on-connect': rdwr_connected},
324281681Srpaulo                                   llcp={'on-startup': llcp_startup,
325281681Srpaulo                                         'on-connect': llcp_connected}):
326281681Srpaulo                    break
327281681Srpaulo            except Exception, e:
328281681Srpaulo                print "clf.connect failed"
329281681Srpaulo
330281681Srpaulo            global srv
331281681Srpaulo            if only_one and srv and srv.success:
332281681Srpaulo                raise SystemExit
333281681Srpaulo
334281681Srpaulo    except KeyboardInterrupt:
335281681Srpaulo        raise SystemExit
336281681Srpaulo    finally:
337281681Srpaulo        clf.close()
338281681Srpaulo
339281681Srpaulo    raise SystemExit
340281681Srpaulo
341281681Srpauloif __name__ == '__main__':
342281681Srpaulo    main()
343