1#!/usr/bin/python
2#
3# Example nfcpy to wpa_supplicant 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 random
13import StringIO
14
15import nfc
16import nfc.ndef
17import nfc.llcp
18import nfc.handover
19
20import logging
21logging.basicConfig()
22
23import wpaspy
24
25wpas_ctrl = '/var/run/wpa_supplicant'
26
27def wpas_connect():
28    ifaces = []
29    if os.path.isdir(wpas_ctrl):
30        try:
31            ifaces = [os.path.join(wpas_ctrl, i) for i in os.listdir(wpas_ctrl)]
32        except OSError, error:
33            print "Could not find wpa_supplicant: ", error
34            return None
35
36    if len(ifaces) < 1:
37        print "No wpa_supplicant control interface found"
38        return None
39
40    for ctrl in ifaces:
41        try:
42            wpas = wpaspy.Ctrl(ctrl)
43            return wpas
44        except Exception, e:
45            pass
46    return None
47
48
49def wpas_tag_read(message):
50    wpas = wpas_connect()
51    if (wpas == None):
52        return False
53    if "FAIL" in wpas.request("WPS_NFC_TAG_READ " + message.encode("hex")):
54        return False
55    return True
56
57def wpas_get_config_token(id=None):
58    wpas = wpas_connect()
59    if (wpas == None):
60        return None
61    if id:
62        ret = wpas.request("WPS_NFC_CONFIG_TOKEN NDEF " + id)
63    else:
64        ret = wpas.request("WPS_NFC_CONFIG_TOKEN NDEF")
65    if "FAIL" in ret:
66        return None
67    return ret.rstrip().decode("hex")
68
69
70def wpas_get_er_config_token(uuid):
71    wpas = wpas_connect()
72    if (wpas == None):
73        return None
74    return wpas.request("WPS_ER_NFC_CONFIG_TOKEN NDEF " + uuid).rstrip().decode("hex")
75
76
77def wpas_get_password_token():
78    wpas = wpas_connect()
79    if (wpas == None):
80        return None
81    return wpas.request("WPS_NFC_TOKEN NDEF").rstrip().decode("hex")
82
83
84def wpas_get_handover_req():
85    wpas = wpas_connect()
86    if (wpas == None):
87        return None
88    return wpas.request("NFC_GET_HANDOVER_REQ NDEF WPS-CR").rstrip().decode("hex")
89
90
91def wpas_get_handover_sel(uuid):
92    wpas = wpas_connect()
93    if (wpas == None):
94        return None
95    if uuid is None:
96        return wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR").rstrip().decode("hex")
97    return wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR " + uuid).rstrip().decode("hex")
98
99
100def wpas_report_handover(req, sel, type):
101    wpas = wpas_connect()
102    if (wpas == None):
103        return None
104    return wpas.request("NFC_REPORT_HANDOVER " + type + " WPS " +
105                        str(req).encode("hex") + " " +
106                        str(sel).encode("hex"))
107
108
109class HandoverServer(nfc.handover.HandoverServer):
110    def __init__(self):
111        super(HandoverServer, self).__init__()
112
113    def process_request(self, request):
114        print "HandoverServer - request received"
115        print "Parsed handover request: " + request.pretty()
116
117        sel = nfc.ndef.HandoverSelectMessage(version="1.2")
118
119        for carrier in request.carriers:
120            print "Remote carrier type: " + carrier.type
121            if carrier.type == "application/vnd.wfa.wsc":
122                print "WPS carrier type match - add WPS carrier record"
123                self.received_carrier = carrier.record
124                data = wpas_get_handover_sel(self.uuid)
125                if data is None:
126                    print "Could not get handover select carrier record from wpa_supplicant"
127                    continue
128                print "Handover select carrier record from wpa_supplicant:"
129                print data.encode("hex")
130                self.sent_carrier = data
131
132                message = nfc.ndef.Message(data);
133                sel.add_carrier(message[0], "active", message[1:])
134
135        print "Handover select:"
136        print sel.pretty()
137        print str(sel).encode("hex")
138
139        print "Sending handover select"
140        return sel
141
142
143def wps_handover_resp(peer, uuid):
144    if uuid is None:
145        print "Trying to handle WPS handover"
146    else:
147        print "Trying to handle WPS handover with AP " + uuid
148
149    srv = HandoverServer()
150    srv.sent_carrier = None
151    srv.uuid = uuid
152
153    nfc.llcp.activate(peer);
154
155    try:
156        print "Trying handover";
157        srv.start()
158        print "Wait for disconnect"
159        while nfc.llcp.connected():
160            time.sleep(0.1)
161        print "Disconnected after handover"
162    except nfc.llcp.ConnectRefused:
163        print "Handover connection refused"
164        nfc.llcp.shutdown()
165        return
166
167    if srv.sent_carrier:
168        wpas_report_handover(srv.received_carrier, srv.sent_carrier, "RESP")
169
170    print "Remove peer"
171    nfc.llcp.shutdown()
172    print "Done with handover"
173    time.sleep(1)
174
175
176def wps_handover_init(peer):
177    print "Trying to initiate WPS handover"
178
179    data = wpas_get_handover_req()
180    if (data == None):
181        print "Could not get handover request carrier record from wpa_supplicant"
182        return
183    print "Handover request carrier record from wpa_supplicant: " + data.encode("hex")
184    record = nfc.ndef.Record()
185    f = StringIO.StringIO(data)
186    record._read(f)
187    record = nfc.ndef.HandoverCarrierRecord(record)
188    print "Parsed handover request carrier record:"
189    print record.pretty()
190
191    message = nfc.ndef.HandoverRequestMessage(version="1.2")
192    message.nonce = random.randint(0, 0xffff)
193    message.add_carrier(record, "active")
194
195    print "Handover request:"
196    print message.pretty()
197
198    nfc.llcp.activate(peer);
199
200    client = nfc.handover.HandoverClient()
201    try:
202        print "Trying handover";
203        client.connect()
204        print "Connected for handover"
205    except nfc.llcp.ConnectRefused:
206        print "Handover connection refused"
207        nfc.llcp.shutdown()
208        client.close()
209        return
210
211    print "Sending handover request"
212
213    if not client.send(message):
214        print "Failed to send handover request"
215
216    print "Receiving handover response"
217    message = client._recv()
218    if message is None:
219        print "No response received"
220        nfc.llcp.shutdown()
221        client.close()
222        return
223    if message.type != "urn:nfc:wkt:Hs":
224        print "Response was not Hs - received: " + message.type
225        nfc.llcp.shutdown()
226        client.close()
227        return
228
229    print "Received message"
230    print message.pretty()
231    message = nfc.ndef.HandoverSelectMessage(message)
232    print "Handover select received"
233    print message.pretty()
234
235    for carrier in message.carriers:
236        print "Remote carrier type: " + carrier.type
237        if carrier.type == "application/vnd.wfa.wsc":
238            print "WPS carrier type match - send to wpa_supplicant"
239            wpas_report_handover(data, carrier.record, "INIT")
240            wifi = nfc.ndef.WifiConfigRecord(carrier.record)
241            print wifi.pretty()
242
243    print "Remove peer"
244    nfc.llcp.shutdown()
245    client.close()
246    print "Done with handover"
247
248
249def wps_tag_read(tag, wait_remove=True):
250    success = False
251    if len(tag.ndef.message):
252        message = nfc.ndef.Message(tag.ndef.message)
253        print "message type " + message.type
254
255        for record in message:
256            print "record type " + record.type
257            if record.type == "application/vnd.wfa.wsc":
258                print "WPS tag - send to wpa_supplicant"
259                success = wpas_tag_read(tag.ndef.message)
260                break
261    else:
262        print "Empty tag"
263
264    if wait_remove:
265        print "Remove tag"
266        while tag.is_present:
267            time.sleep(0.1)
268
269    return success
270
271
272def wps_write_config_tag(clf, id=None, wait_remove=True):
273    print "Write WPS config token"
274    data = wpas_get_config_token(id)
275    if (data == None):
276        print "Could not get WPS config token from wpa_supplicant"
277        sys.exit(1)
278        return
279
280    print "Touch an NFC tag"
281    while True:
282        tag = clf.poll()
283        if tag == None:
284            time.sleep(0.1)
285            continue
286        break
287
288    print "Tag found - writing"
289    tag.ndef.message = data
290    print "Done - remove tag"
291    while wait_remove and tag.is_present:
292        time.sleep(0.1)
293
294
295def wps_write_er_config_tag(clf, uuid):
296    print "Write WPS ER config token"
297    data = wpas_get_er_config_token(uuid)
298    if (data == None):
299        print "Could not get WPS config token from wpa_supplicant"
300        return
301
302    print "Touch an NFC tag"
303    while True:
304        tag = clf.poll()
305        if tag == None:
306            time.sleep(0.1)
307            continue
308        break
309
310    print "Tag found - writing"
311    tag.ndef.message = data
312    print "Done - remove tag"
313    while tag.is_present:
314        time.sleep(0.1)
315
316
317def wps_write_password_tag(clf, wait_remove=True):
318    print "Write WPS password token"
319    data = wpas_get_password_token()
320    if (data == None):
321        print "Could not get WPS password token from wpa_supplicant"
322        return
323
324    print "Touch an NFC tag"
325    while True:
326        tag = clf.poll()
327        if tag == None:
328            time.sleep(0.1)
329            continue
330        break
331
332    print "Tag found - writing"
333    tag.ndef.message = data
334    print "Done - remove tag"
335    while wait_remove and tag.is_present:
336        time.sleep(0.1)
337
338
339def find_peer(clf):
340    while True:
341        if nfc.llcp.connected():
342            print "LLCP connected"
343        general_bytes = nfc.llcp.startup({})
344        peer = clf.listen(ord(os.urandom(1)) + 250, general_bytes)
345        if isinstance(peer, nfc.DEP):
346            print "listen -> DEP";
347            if peer.general_bytes.startswith("Ffm"):
348                print "Found DEP"
349                return peer
350            print "mismatch in general_bytes"
351            print peer.general_bytes
352
353        peer = clf.poll(general_bytes)
354        if isinstance(peer, nfc.DEP):
355            print "poll -> DEP";
356            if peer.general_bytes.startswith("Ffm"):
357                print "Found DEP"
358                return peer
359            print "mismatch in general_bytes"
360            print peer.general_bytes
361
362        if peer:
363            print "Found tag"
364            return peer
365
366
367def main():
368    clf = nfc.ContactlessFrontend()
369
370    try:
371        arg_uuid = None
372        if len(sys.argv) > 1 and sys.argv[1] != '-1':
373            arg_uuid = sys.argv[1]
374
375        if len(sys.argv) > 1 and sys.argv[1] == '-1':
376            only_one = True
377        else:
378            only_one = False
379
380        if len(sys.argv) > 1 and sys.argv[1] == "write-config":
381            wps_write_config_tag(clf)
382            raise SystemExit
383
384        if len(sys.argv) > 1 and sys.argv[1] == "write-config-no-wait":
385            wps_write_config_tag(clf, wait_remove=False)
386            raise SystemExit
387
388        if len(sys.argv) > 2 and sys.argv[1] == "write-config-id":
389            wps_write_config_tag(clf, sys.argv[2])
390            raise SystemExit
391
392        if len(sys.argv) > 2 and sys.argv[1] == "write-er-config":
393            wps_write_er_config_tag(clf, sys.argv[2])
394            raise SystemExit
395
396        if len(sys.argv) > 1 and sys.argv[1] == "write-password":
397            wps_write_password_tag(clf)
398            raise SystemExit
399
400        if len(sys.argv) > 1 and sys.argv[1] == "write-password-no-wait":
401            wps_write_password_tag(clf, wait_remove=False)
402            raise SystemExit
403
404        while True:
405            print "Waiting for a tag or peer to be touched"
406
407            tag = find_peer(clf)
408            if isinstance(tag, nfc.DEP):
409                if arg_uuid is None:
410                    wps_handover_init(tag)
411                elif arg_uuid is "ap":
412                    wps_handover_resp(tag, None)
413                else:
414                    wps_handover_resp(tag, arg_uuid)
415                if only_one:
416                    break
417                continue
418
419            if tag.ndef:
420                success = wps_tag_read(tag, not only_one)
421                if only_one:
422                    if not success:
423                        sys.exit(1)
424                    break
425                continue
426
427            print "Not an NDEF tag - remove tag"
428            if only_one:
429                sys.exit(1)
430            while tag.is_present:
431                time.sleep(0.1)
432
433    except KeyboardInterrupt:
434        raise SystemExit
435    finally:
436        clf.close()
437
438    raise SystemExit
439
440if __name__ == '__main__':
441    main()
442