Updated through tag hostap_2_5 from git://w1.fi/hostap.git
[mech_eap.git] / libeap / hostapd / wps-ap-nfc.py
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
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()