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