NFC: Add summary and success file options for nfcpy scripts
[mech_eap.git] / 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     def process_request(self, request):
117         summary("HandoverServer - request received")
118         try:
119             print "Parsed handover request: " + request.pretty()
120         except Exception, e:
121             print e
122         print str(request).encode("hex")
123
124         sel = nfc.ndef.HandoverSelectMessage(version="1.2")
125
126         for carrier in request.carriers:
127             print "Remote carrier type: " + carrier.type
128             if carrier.type == "application/vnd.wfa.wsc":
129                 summary("WPS carrier type match - add WPS carrier record")
130                 data = wpas_get_handover_sel()
131                 if data is None:
132                     summary("Could not get handover select carrier record from hostapd")
133                     continue
134                 print "Handover select carrier record from hostapd:"
135                 print data.encode("hex")
136                 if "OK" in wpas_report_handover(carrier.record, data):
137                     success_report("Handover reported successfully")
138                 else:
139                     summary("Handover report rejected")
140
141                 message = nfc.ndef.Message(data);
142                 sel.add_carrier(message[0], "active", message[1:])
143
144         print "Handover select:"
145         try:
146             print sel.pretty()
147         except Exception, e:
148             print e
149         print str(sel).encode("hex")
150
151         summary("Sending handover select")
152         self.success = True
153         return sel
154
155
156 def wps_tag_read(tag):
157     success = False
158     if len(tag.ndef.message):
159         for record in tag.ndef.message:
160             print "record type " + record.type
161             if record.type == "application/vnd.wfa.wsc":
162                 summary("WPS tag - send to hostapd")
163                 success = wpas_tag_read(tag.ndef.message)
164                 break
165     else:
166         summary("Empty tag")
167
168     if success:
169         success_report("Tag read succeeded")
170
171     return success
172
173
174 def rdwr_connected_write(tag):
175     summary("Tag found - writing - " + str(tag))
176     global write_data
177     tag.ndef.message = str(write_data)
178     success_report("Tag write succeeded")
179     print "Done - remove tag"
180     global only_one
181     if only_one:
182         global continue_loop
183         continue_loop = False
184     global write_wait_remove
185     while write_wait_remove and tag.is_present:
186         time.sleep(0.1)
187
188 def wps_write_config_tag(clf, wait_remove=True):
189     summary("Write WPS config token")
190     global write_data, write_wait_remove
191     write_wait_remove = wait_remove
192     write_data = wpas_get_config_token()
193     if write_data == None:
194         summary("Could not get WPS config token from hostapd")
195         return
196
197     print "Touch an NFC tag"
198     clf.connect(rdwr={'on-connect': rdwr_connected_write})
199
200
201 def wps_write_password_tag(clf, wait_remove=True):
202     summary("Write WPS password token")
203     global write_data, write_wait_remove
204     write_wait_remove = wait_remove
205     write_data = wpas_get_password_token()
206     if write_data == None:
207         summary("Could not get WPS password token from hostapd")
208         return
209
210     print "Touch an NFC tag"
211     clf.connect(rdwr={'on-connect': rdwr_connected_write})
212
213
214 def rdwr_connected(tag):
215     global only_one, no_wait
216     summary("Tag connected: " + str(tag))
217
218     if tag.ndef:
219         print "NDEF tag: " + tag.type
220         try:
221             print tag.ndef.message.pretty()
222         except Exception, e:
223             print e
224         success = wps_tag_read(tag)
225         if only_one and success:
226             global continue_loop
227             continue_loop = False
228     else:
229         summary("Not an NDEF tag - remove tag")
230         return True
231
232     return not no_wait
233
234
235 def llcp_startup(clf, llc):
236     print "Start LLCP server"
237     global srv
238     srv = HandoverServer(llc)
239     return llc
240
241 def llcp_connected(llc):
242     print "P2P LLCP connected"
243     global wait_connection
244     wait_connection = False
245     global srv
246     srv.start()
247     return True
248
249
250 def main():
251     clf = nfc.ContactlessFrontend()
252
253     parser = argparse.ArgumentParser(description='nfcpy to hostapd integration for WPS NFC operations')
254     parser.add_argument('-d', const=logging.DEBUG, default=logging.INFO,
255                         action='store_const', dest='loglevel',
256                         help='verbose debug output')
257     parser.add_argument('-q', const=logging.WARNING, action='store_const',
258                         dest='loglevel', help='be quiet')
259     parser.add_argument('--only-one', '-1', action='store_true',
260                         help='run only one operation and exit')
261     parser.add_argument('--no-wait', action='store_true',
262                         help='do not wait for tag to be removed before exiting')
263     parser.add_argument('--summary',
264                         help='summary file for writing status updates')
265     parser.add_argument('--success',
266                         help='success file for writing success update')
267     parser.add_argument('command', choices=['write-config',
268                                             'write-password'],
269                         nargs='?')
270     args = parser.parse_args()
271
272     global only_one
273     only_one = args.only_one
274
275     global no_wait
276     no_wait = args.no_wait
277
278     if args.summary:
279         global summary_file
280         summary_file = args.summary
281
282     if args.success:
283         global success_file
284         success_file = args.success
285
286     logging.basicConfig(level=args.loglevel)
287
288     try:
289         if not clf.open("usb"):
290             print "Could not open connection with an NFC device"
291             raise SystemExit
292
293         if args.command == "write-config":
294             wps_write_config_tag(clf, wait_remove=not args.no_wait)
295             raise SystemExit
296
297         if args.command == "write-password":
298             wps_write_password_tag(clf, wait_remove=not args.no_wait)
299             raise SystemExit
300
301         global continue_loop
302         while continue_loop:
303             print "Waiting for a tag or peer to be touched"
304             wait_connection = True
305             try:
306                 if not clf.connect(rdwr={'on-connect': rdwr_connected},
307                                    llcp={'on-startup': llcp_startup,
308                                          'on-connect': llcp_connected}):
309                     break
310             except Exception, e:
311                 print "clf.connect failed"
312
313             global srv
314             if only_one and srv and srv.success:
315                 raise SystemExit
316
317     except KeyboardInterrupt:
318         raise SystemExit
319     finally:
320         clf.close()
321
322     raise SystemExit
323
324 if __name__ == '__main__':
325     main()