nexmon – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 #!/usr/bin/python
2  
3 import sys
4 import os
5 import subprocess
6 import random
7 import time
8 import sqlite3
9 import threading
10 import hashlib
11 import gzip
12 import json
13 import datetime
14 import re
15  
16 if sys.version_info[0] >= 3:
17 from socketserver import ThreadingTCPServer
18 from urllib.request import urlopen, URLError
19 from urllib.parse import urlparse, parse_qs
20 from http.client import HTTPConnection
21 from http.server import SimpleHTTPRequestHandler
22 else:
23 from SocketServer import ThreadingTCPServer
24 from urllib2 import urlopen, URLError
25 from urlparse import urlparse, parse_qs
26 from httplib import HTTPConnection
27 from SimpleHTTPServer import SimpleHTTPRequestHandler
28  
29 bytes = lambda a, b : a
30  
31 port = 1337
32 url = None
33 cid = None
34 tls = threading.local()
35 nets = {}
36 cracker = None
37  
38 class ServerHandler(SimpleHTTPRequestHandler):
39 def do_GET(s):
40 result = s.do_req(s.path)
41  
42 if not result:
43 return
44  
45 s.send_response(200)
46 s.send_header("Content-type", "text/plain")
47 s.end_headers()
48 s.wfile.write(bytes(result, "UTF-8"))
49  
50 def do_POST(s):
51 if ("dict" in s.path):
52 s.do_upload_dict()
53  
54 if ("cap" in s.path):
55 s.do_upload_cap()
56  
57 s.send_response(200)
58 s.send_header("Content-type", "text/plain")
59 s.end_headers()
60 s.wfile.write(bytes("OK", "UTF-8"))
61  
62 def do_upload_dict(s):
63 con = get_con()
64  
65 f = "dcrack-dict"
66 c = f + ".gz"
67 o = open(c, "wb")
68 cl = int(s.headers['Content-Length'])
69 o.write(s.rfile.read(cl))
70 o.close()
71  
72 decompress(f)
73  
74 sha1 = hashlib.sha1()
75 x = open(f, "rb")
76 sha1.update(x.read())
77 x.close()
78 h = sha1.hexdigest()
79  
80 x = open(f, "rb")
81 for i, l in enumerate(x):
82 pass
83  
84 i = i + 1
85 x.close()
86  
87 n = "%s-%s.txt" % (f, h)
88 os.rename(f, n)
89 os.rename(c, "%s.gz" % n)
90  
91 c = con.cursor()
92 c.execute("INSERT into dict values (?, ?, 0)", (h, i))
93 con.commit()
94  
95 def do_upload_cap(s):
96 cl = int(s.headers['Content-Length'])
97 f = open("dcrack.cap.tmp.gz", "wb")
98 f.write(s.rfile.read(cl))
99 f.close()
100  
101 decompress("dcrack.cap.tmp")
102 os.rename("dcrack.cap.tmp.gz", "dcrack.cap.gz")
103 os.rename("dcrack.cap.tmp", "dcrack.cap")
104  
105 def do_req(s, path):
106 con = get_con()
107  
108 c = con.cursor()
109  
110 c.execute("""DELETE from clients where
111 (strftime('%s', datetime()) - strftime('%s', last))
112 > 300""")
113  
114 con.commit()
115  
116 if ("ping" in path):
117 return s.do_ping(path)
118  
119 if ("getwork" in path):
120 return s.do_getwork(path)
121  
122 if ("dict" in path and "status" in path):
123 return s.do_dict_status(path)
124  
125 if ("dict" in path and "set" in path):
126 return s.do_dict_set(path)
127  
128 if ("dict" in path):
129 return s.get_dict(path)
130  
131 if ("net" in path and "/crack" in path):
132 return s.do_crack(path)
133  
134 if ("net" in path and "result" in path):
135 return s.do_result(path)
136  
137 if ("cap" in path):
138 return s.get_cap(path)
139  
140 if ("status" in path):
141 return s.get_status()
142  
143 if ("remove" in path):
144 return s.remove(path)
145  
146 return "error"
147  
148 def remove(s, path):
149 con = get_con()
150  
151 p = path.split("/")
152 n = p[4].upper()
153  
154 c = con.cursor()
155 c.execute("DELETE from nets where bssid = ?", (n,))
156 con.commit()
157  
158 c.execute("DELETE from work where net = ?", (n,))
159 con.commit()
160  
161 return "OK"
162  
163 def get_status(s):
164 con = get_con()
165  
166 c = con.cursor()
167 c.execute("SELECT * from clients")
168  
169 clients = []
170  
171 for r in c.fetchall():
172 clients.append(r['speed'])
173  
174 nets = []
175  
176 c.execute("SELECT * from dict where current = 1")
177 dic = c.fetchone()
178  
179 c.execute("SELECT * from nets")
180  
181 for r in c.fetchall():
182 n = { "bssid" : r['bssid'] }
183 if r['pass']:
184 n["pass"] = r['pass']
185  
186 if r['state'] != 2:
187 n["tot"] = dic["lines"]
188  
189 did = 0
190 cur = con.cursor()
191 cur.execute("""SELECT * from work where net = ?
192 and dict = ? and state = 2""",
193 (n['bssid'], dic['id']))
194 for row in cur.fetchall():
195 did += row['end'] - row['start']
196  
197 n["did"] = did
198  
199 nets.append(n)
200  
201 d = { "clients" : clients, "nets" : nets }
202  
203 return json.dumps(d)
204  
205 def do_result_pass(s, net, pw):
206 con = get_con()
207  
208 pf = "dcrack-pass.txt"
209  
210 f = open(pf, "w")
211 f.write(pw)
212 f.write("\n")
213 f.close()
214  
215 cmd = ["aircrack-ng", "-w", pf, "-b", net, "-q", "dcrack.cap"]
216 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, \
217 stdin=subprocess.PIPE)
218  
219 res = p.communicate()[0]
220 res = str(res)
221  
222 os.remove(pf)
223  
224 if not "KEY FOUND" in res:
225 return "error"
226  
227 s.net_done(net)
228  
229 c = con.cursor()
230 c.execute("UPDATE nets set pass = ? where bssid = ?", \
231 (pw, net))
232  
233 con.commit()
234  
235 return "OK"
236  
237 def net_done(s, net):
238 con = get_con()
239  
240 c = con.cursor()
241 c.execute("UPDATE nets set state = 2 where bssid = ?",
242 (net,))
243  
244 c.execute("DELETE from work where net = ?", (net,))
245 con.commit()
246  
247 def do_result(s, path):
248 con = get_con()
249  
250 p = path.split("/")
251 n = p[4].upper()
252  
253 x = urlparse(path)
254 qs = parse_qs(x.query)
255  
256 if "pass" in qs:
257 return s.do_result_pass(n, qs['pass'][0])
258  
259 wl = qs['wl'][0]
260  
261 c = con.cursor()
262 c.execute("SELECT * from nets where bssid = ?", (n,))
263 r = c.fetchone()
264 if r and r['state'] == 2:
265 return "Already done"
266  
267 c.execute("""UPDATE work set state = 2 where
268 net = ? and dict = ? and start = ? and end = ?""",
269 (n, wl, qs['start'][0], qs['end'][0]))
270  
271 con.commit()
272  
273 if c.rowcount == 0:
274 c.execute("""INSERT into work values
275 (NULL, ?, ?, ?, ?, datetime(), 2)""",
276 (n, wl, qs['start'][0], qs['end'][0]))
277 con.commit()
278  
279 # check status
280 c.execute("""SELECT * from work where net = ? and dict = ?
281 and state = 2 order by start""", (n, wl))
282  
283 i = 0
284 r = c.fetchall()
285 for row in r:
286 if i == row['start']:
287 i = row['end']
288 else:
289 break
290  
291 c.execute("SELECT * from dict where id = ? and lines = ?",
292 (wl, i))
293  
294 r = c.fetchone()
295  
296 if r:
297 s.net_done(n)
298  
299 return "OK"
300  
301 def get_cap(s, path):
302 return s.serve_file("dcrack.cap.gz")
303  
304 def get_dict(s, path):
305 p = path.split("/")
306 n = p[4]
307  
308 fn = "dcrack-dict-%s.txt.gz" % n
309  
310 return s.serve_file(fn)
311  
312 def serve_file(s, fn):
313 s.send_response(200)
314 s.send_header("Content-type", "application/x-gzip")
315 s.end_headers()
316  
317 # XXX openat
318 f = open(fn, "rb")
319 s.wfile.write(f.read())
320 f.close()
321  
322 return None
323  
324 def do_crack(s, path):
325 con = get_con()
326  
327 p = path.split("/")
328  
329 n = p[4].upper()
330  
331 c = con.cursor()
332 c.execute("INSERT into nets values (?, NULL, 1)", (n,))
333 con.commit()
334  
335 return "OK"
336  
337 def do_dict_set(s, path):
338 con = get_con()
339  
340 p = path.split("/")
341  
342 h = p[4]
343  
344 c = con.cursor()
345 c.execute("UPDATE dict set current = 0")
346 c.execute("UPDATE dict set current = 1 where id = ?", (h,))
347 con.commit()
348  
349 return "OK"
350  
351 def do_ping(s, path):
352 con = get_con()
353  
354 p = path.split("/")
355  
356 cid = p[4]
357  
358 x = urlparse(path)
359 qs = parse_qs(x.query)
360  
361 speed = qs['speed'][0]
362  
363 c = con.cursor()
364 c.execute("SELECT * from clients where id = ?", (cid,))
365 r = c.fetchall()
366 if (not r):
367 c.execute("INSERT into clients values (?, ?, datetime())",
368 (cid, int(speed)))
369 else:
370 c.execute("""UPDATE clients set speed = ?,
371 last = datetime() where id = ?""",
372 (int(speed), cid))
373  
374 con.commit()
375  
376 return "60"
377  
378 def try_network(s, net, d):
379 con = get_con()
380  
381 c = con.cursor()
382 c.execute("""SELECT * from work where net = ? and dict = ?
383 order by start""", (net['bssid'], d['id']))
384  
385 r = c.fetchall()
386  
387 s = 5000000
388 i = 0
389 found = False
390  
391 for row in r:
392 if found:
393 if i + s > row['start']:
394 s = row['start'] - i
395 break
396  
397 if (i >= row['start'] and i <= row['end']):
398 i = row['end']
399 else:
400 found = True
401  
402 if i + s > d['lines']:
403 s = d['lines'] - i
404  
405 if s == 0:
406 return None
407  
408 c.execute("INSERT into work values (NULL, ?, ?, ?, ?, datetime(), 1)",
409 (net['bssid'], d['id'], i, i + s))
410  
411 con.commit()
412  
413 crack = { "net" : net['bssid'], \
414 "dict" : d['id'], \
415 "start" : i, \
416 "end" : i + s }
417  
418 j = json.dumps(crack)
419  
420 return j
421  
422 def do_getwork(s, path):
423 con = get_con()
424  
425 c = con.cursor()
426  
427 c.execute("""DELETE from work where
428 ((strftime('%s', datetime()) - strftime('%s', last))
429 > 3600) and state = 1""")
430  
431 con.commit()
432  
433 c.execute("SELECT * from dict where current = 1")
434 d = c.fetchone()
435  
436 c.execute("SELECT * from nets where state = 1")
437 r = c.fetchall()
438  
439 for row in r:
440 res = s.try_network(row, d)
441 if res:
442 return res
443  
444 # try some old stuff
445 c.execute("""select * from work where state = 1
446 order by last limit 1""")
447  
448 res = c.fetchone()
449  
450 if res:
451 c.execute("DELETE from work where id = ?", (res['id'],))
452 for row in r:
453 res = s.try_network(row, d)
454 if res:
455 return res
456  
457 res = { "interval" : "60" }
458  
459 return json.dumps(res)
460  
461 def do_dict_status(s, path):
462 p = path.split("/")
463  
464 d = p[4]
465  
466 try:
467 f = open("dcrack-dict-%s.txt" % d)
468 f.close()
469  
470 return "OK"
471 except:
472 return "NO"
473  
474 def create_db():
475 con = get_con()
476  
477 c = con.cursor()
478 c.execute("""create table clients (id varchar(255),
479 speed integer, last datetime)""")
480  
481 c.execute("""create table dict (id varchar(255), lines integer,
482 current boolean)""")
483 c.execute("""create table nets (bssid varchar(255), pass varchar(255),
484 state integer)""")
485  
486 c.execute("""create table work (id integer primary key,
487 net varchar(255), dict varchar(255),
488 start integer, end integer, last datetime, state integer)""")
489  
490 def connect_db():
491 con = sqlite3.connect('dcrack.db')
492 con.row_factory = sqlite3.Row
493  
494 return con
495  
496 def get_con():
497 global tls
498  
499 try:
500 return tls.con
501 except:
502 tls.con = connect_db()
503 return tls.con
504  
505 def init_db():
506 con = get_con()
507 c = con.cursor()
508  
509 try:
510 c.execute("SELECT * from clients")
511 except:
512 create_db()
513  
514 def server():
515 init_db()
516  
517 server_class = ThreadingTCPServer
518 httpd = server_class(('', port), ServerHandler)
519  
520 print("Starting server")
521 httpd.serve_forever()
522  
523 def usage():
524 print("""dcrack v0.3
525  
526 Usage: dcrack.py [MODE]
527 server Runs coordinator
528 client <server addr> Runs cracker
529 cmd <server addr> [CMD] Sends a command to server
530  
531 [CMD] can be:
532 dict <file>
533 cap <file>
534 crack <bssid>
535 remove <bssid>
536 status""")
537 exit(1)
538  
539 def get_speed():
540 print("Getting speed")
541 p = subprocess.Popen(["aircrack-ng", "-S"], stdout=subprocess.PIPE)
542 speed = p.stdout.readline()
543 speed = speed.split()
544 speed = speed[len(speed) - 2]
545 return int(speed)
546  
547 def get_cid():
548 return random.getrandbits(64)
549  
550 def do_ping(speed):
551 global url, cid
552  
553 u = url + "client/" + str(cid) + "/ping?speed=" + str(speed)
554 stuff = urlopen(u).read()
555 interval = int(stuff)
556  
557 return interval
558  
559 def pinger(speed):
560 while True:
561 interval = try_ping(speed)
562 time.sleep(interval)
563  
564 def try_ping(speed):
565 while True:
566 try:
567 return do_ping(speed)
568 except URLError:
569 print("Conn refused (pinger)")
570 time.sleep(60)
571  
572 def get_work():
573 global url, cid, cracker
574  
575 u = url + "client/" + str(cid) + "/getwork"
576 stuff = urlopen(u).read()
577 stuff = stuff.decode("utf-8")
578  
579 crack = json.loads(stuff)
580  
581 if "interval" in crack:
582 print("Waiting")
583 return int(crack['interval'])
584  
585 wl = setup_dict(crack)
586 cap = get_cap(crack)
587  
588 print("Cracking")
589  
590 cmd = ["aircrack-ng", "-w", wl, "-b", crack['net'], "-q", cap]
591  
592 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, \
593 stdin=subprocess.PIPE)
594  
595 cracker = p
596  
597 res = p.communicate()[0]
598 res = str(res)
599  
600 cracker = None
601  
602 if ("not in dictionary" in res):
603 print("No luck")
604 u = "%snet/%s/result?wl=%s&start=%d&end=%d&found=0" % \
605 (url, crack['net'], crack['dict'], \
606 crack['start'], crack['end'])
607  
608 stuff = urlopen(u).read()
609 elif "KEY FOUND" in res:
610 pw = re.sub("^.*\[ ", "", res)
611  
612 i = pw.rfind(" ]")
613 if i == -1:
614 raise BaseException("Can't parse output")
615  
616 pw = pw[:i]
617  
618 print("Key for %s is %s" % (crack['net'], pw))
619  
620 u = "%snet/%s/result?pass=%s" % (url, crack['net'], pw)
621 stuff = urlopen(u).read()
622  
623 return 0
624  
625 def decompress(fn):
626 f = gzip.open(fn + ".gz")
627 o = open(fn, "wb")
628 o.writelines(f)
629 o.close()
630 f.close()
631  
632 def setup_dict(crack):
633 global url
634  
635 d = crack['dict']
636  
637 fn = "dcrack-client-dict-%s.txt" % d
638  
639 try:
640 f = open(fn)
641 f.close()
642 except:
643 print("Downloading dictionary %s" % d)
644  
645 u = "%sdict/%s" % (url, d)
646 stuff = urlopen(u)
647  
648 f = open(fn + ".gz", "wb")
649 f.write(stuff.read())
650 f.close()
651  
652 print("Uncompressing dictionary")
653 decompress(fn)
654  
655 sha1 = hashlib.sha1()
656 f = open(fn, "rb")
657 sha1.update(f.read())
658 f.close()
659 h = sha1.hexdigest()
660  
661 if h != d:
662 print("bad dictionary")
663 exit(1)
664  
665 s = "dcrack-client-dict-%s-%d:%d.txt" \
666 % (d, crack['start'], crack['end'])
667  
668 try:
669 f = open(s)
670 f.close()
671 except:
672 print("Splitting dict %s" % s)
673 f = open(fn, "rb")
674 o = open(s, "wb")
675  
676 for i, l in enumerate(f):
677 if i >= crack['end']:
678 break
679  
680 if i >= crack['start']:
681 o.write(l)
682  
683 f.close()
684 o.close()
685  
686 return s
687  
688 def get_cap(crack):
689 global url, nets
690  
691 fn = "dcrack-client.cap"
692  
693 bssid = crack['net'].upper()
694  
695 if bssid in nets:
696 return fn
697  
698 try:
699 f = open(fn, "rb")
700 f.close()
701 check_cap(fn, bssid)
702 except:
703 pass
704  
705 if bssid in nets:
706 return fn
707  
708 print("Downloading cap")
709 u = "%scap/%s" % (url, bssid)
710  
711 stuff = urlopen(u)
712  
713 f = open(fn + ".gz", "wb")
714 f.write(stuff.read())
715 f.close()
716  
717 print("Uncompressing cap")
718 decompress(fn)
719  
720 nets = {}
721 check_cap(fn, bssid)
722  
723 if bssid not in nets:
724 raise BaseException("Can't find net %s" % bssid)
725  
726 return fn
727  
728 def process_cap(fn):
729 global nets
730  
731 nets = {}
732  
733 print("Processing cap")
734 p = subprocess.Popen(["aircrack-ng", fn], stdout=subprocess.PIPE, \
735 stdin=subprocess.PIPE)
736 found = False
737 while True:
738 line = p.stdout.readline()
739  
740 try:
741 line = line.decode("utf-8")
742 except:
743 line = str(line)
744  
745 if "1 handshake" in line:
746 found = True
747 parts = line.split()
748 b = parts[1].upper()
749 # print("BSSID [%s]" % b)
750 nets[b] = True
751  
752 if (found and line == "\n"):
753 break
754  
755 p.stdin.write(bytes("1\n", "utf-8"))
756 p.communicate()
757  
758 def check_cap(fn, bssid):
759 global nets
760  
761 cmd = ["aircrack-ng", "-b", bssid, fn]
762 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
763  
764 res = p.communicate()[0]
765 res = str(res)
766  
767 if "No matching network found" not in res:
768 nets[bssid] = True
769  
770 def worker():
771 while True:
772 interval = get_work()
773 time.sleep(interval)
774  
775 def set_url():
776 global url, port
777  
778 if len(sys.argv) < 3:
779 print("Provide server addr")
780 usage()
781  
782 host = sys.argv[2]
783  
784 if not ":" in host:
785 host = "%s:%d" % (host, port)
786  
787 url = "http://" + host + "/" + "dcrack/"
788  
789 def client():
790 global cid, cracker, url
791  
792 set_url()
793 url += "worker/"
794  
795 speed = get_speed()
796 print("Speed", speed)
797  
798 cid = get_cid()
799  
800 print("CID", cid)
801  
802 try_ping(speed)
803 t = threading.Thread(target=pinger, args=(speed,))
804 t.start()
805  
806 while True:
807 try:
808 do_client()
809 break
810 except URLError:
811 print("Conn refused")
812 time.sleep(60)
813  
814 def do_client():
815 try:
816 worker()
817 except KeyboardInterrupt:
818 if cracker:
819 cracker.kill()
820 print("one more time...")
821  
822 def upload_file(url, f):
823 x = urlparse(url)
824 c = HTTPConnection(x.netloc)
825  
826 # XXX not quite HTTP form
827  
828 f = open(f, "rb")
829 c.request("POST", x.path, f)
830 res = c.getresponse()
831 stuff = res.read()
832 c.close()
833 f.close()
834  
835 return stuff
836  
837 def compress_file(f):
838 i = open(f, "rb")
839 o = gzip.open(f + ".gz", "wb")
840 o.writelines(i)
841 o.close()
842 i.close()
843  
844 def send_dict():
845 global url
846  
847 if len(sys.argv) < 5:
848 print("Need dict")
849 usage()
850  
851 d = sys.argv[4]
852  
853 print("Calculating dictionary hash for %s" % d)
854  
855 sha1 = hashlib.sha1()
856 f = open(d, "rb")
857 sha1.update(f.read())
858 f.close()
859  
860 h = sha1.hexdigest()
861  
862 print("Hash is %s" % h)
863  
864 u = url + "dict/" + h + "/status"
865 stuff = urlopen(u).read()
866  
867 if "NO" in str(stuff):
868 u = url + "dict/create"
869 print("Compressing dictionary")
870 compress_file(d)
871 print("Uploading dictionary")
872 upload_file(u, d + ".gz")
873  
874 print("Setting dictionary to %s" % d)
875 u = url + "dict/" + h + "/set"
876 stuff = urlopen(u).read()
877  
878 def send_cap():
879 global url
880  
881 if len(sys.argv) < 5:
882 print("Need cap")
883 usage()
884  
885 cap = sys.argv[4]
886  
887 print("Cleaning cap %s" % cap)
888 subprocess.Popen(["wpaclean", cap + ".clean", cap], \
889 stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]
890  
891 print("Compressing cap")
892 compress_file(cap + ".clean")
893  
894 u = url + "cap/create"
895 upload_file(u, cap + ".clean.gz")
896  
897 def cmd_crack():
898 net_cmd("crack")
899  
900 def net_cmd(op):
901 global url
902  
903 if len(sys.argv) < 5:
904 print("Need BSSID")
905 usage()
906  
907 bssid = sys.argv[4]
908  
909 print("%s %s" % (op, bssid))
910 u = "%snet/%s/%s" % (url, bssid, op)
911 stuff = urlopen(u).read()
912  
913 def cmd_remove():
914 net_cmd("remove")
915  
916 def cmd_status():
917 u = "%sstatus" % url
918 stuff = urlopen(u).read()
919  
920 stuff = json.loads(stuff.decode("utf-8"))
921  
922 # print(stuff)
923 # print("=============")
924  
925 i = 0
926 speed = 0
927 for c in stuff['clients']:
928 i += 1
929 speed += c
930  
931 print("Clients\t%d\nSpeed\t%d\n" % (i, speed))
932  
933 need = 0
934  
935 for n in stuff['nets']:
936 out = n['bssid'] + " "
937  
938 if "pass" in n:
939 out += n['pass']
940 elif "did" in n:
941 did = int(float(n['did']) / float(n['tot']) * 100.0)
942 out += str(did) + "%"
943 need += n['tot'] - n['did']
944 else:
945 out += "-"
946  
947 print(out)
948  
949 if need != 0:
950 print("\nKeys left %d" % need)
951 if speed != 0:
952 s = int(float(need) / float(speed))
953 sec = datetime.timedelta(seconds=s)
954 d = datetime.datetime(1,1,1) + sec
955 print("ETA %dh %dm" % (d.hour, d.minute))
956  
957 def do_cmd():
958 global url
959  
960 set_url()
961 url += "cmd/"
962  
963 if len(sys.argv) < 4:
964 print("Need CMD")
965 usage()
966  
967 cmd = sys.argv[3]
968  
969 if "dict" in cmd:
970 send_dict()
971 elif "cap" in cmd:
972 send_cap()
973 elif "crack" in cmd:
974 cmd_crack()
975 elif "status" in cmd:
976 cmd_status()
977 elif "remove" in cmd:
978 cmd_remove()
979 else:
980 print("Unknown cmd %s" % cmd)
981 usage()
982  
983 def main():
984 if len(sys.argv) < 2:
985 usage()
986  
987 cmd = sys.argv[1]
988  
989 if cmd == "server":
990 server()
991 elif cmd == "client":
992 client()
993 elif cmd == "cmd":
994 do_cmd()
995 else:
996 print("Unknown cmd", cmd)
997 usage()
998  
999 exit(0)
1000  
1001 if __name__ == "__main__":
1002 main()