#!/usr/bin/python
# kdaemon.py: portknocking server
# This code is heavily based on the work of Marilen Corciovei
# Read the original at: http://len.is-a-geek.org/misc/portknock.html
import time, os, sys
# User-configurable
OUTLOG_FILE = '/var/log/knockd.log' # log to write to
INLOG_FILE = '/var/log/knock' # log to read from
open_portseq = [4005, 40034, 9001, 5674] # the sequence of your open knock
close_portseq = [4002, 45034, 9501] # the sequence of your close knock
openport = 22 # the port to open
timeout = 10 # timeout between first and last knock
iface = "eth1" # interface to open port on
iptables = "/sbin/iptables" # location of iptables
chain = "knock" # iptables chain to append to
class gatekeeper:
def __init__(self, olog):
self.ilog = open(INLOG_FILE, 'r')
self.olog = olog
self.restart = 0
self.key = self.initKey()
def __del__(self):
self.ilog.close()
def initKey(self):
'''
Initialize/reset the key
Key structure:
self.key[0][0-n] Open tumbler: one zero for each port in open_portseq
[1][0-n] Close tumbler: one zero for each port in close_portseq
[2] IP address of remote connection
[3][1:-n] Chronological list of port knocks
[4][0] Time first knock received
[4][1] Time last knock received
'''
self.key = [[],[],None,[None],[None, None]]
for i in open_portseq:
self.key[0].append(0)
for i in close_portseq:
self.key[1].append(0)
return self.key
def tailLog(self):
'''
This is the main server loop
'''
watcher = os.stat(INLOG_FILE)
this_modified = last_modified = watcher.st_mtime
self.ilog.seek(0,2)
while True:
if self.restart == 1:
time.sleep(5) # hack for clean reset
break
if this_modified > last_modified:
last_modified = this_modified
self.key = self.parseLog()
self.key = self.checkKeys()
watcher = os.stat(INLOG_FILE)
this_modified = watcher.st_mtime
time.sleep(1)
def parseLog(self):
'''
Loop over lines in the log, if modified
'''
while True:
line = self.ilog.readline().split(" ")
if not line:
break
for f in line:
if "DPT=" in f:
portnum = f[4:]
if "SRC=" in f:
ip = f[4:]
if len(f.split(":")) == 3:
logtime = f
thetime = self.gettime(logtime)
# print self.key # debug?
if self.key[2] == None:
self.key[2] = ip
if int(portnum) in open_portseq:
if self.key[2] == ip:
self.olog.write("%s Host %s sent open knock: %s\n" % \
(time.asctime(time.localtime()), ip, portnum))
if not 1 in self.key[0]: # first knock received
self.key[4][0] = thetime # record time
if not self.key[3][-1] == int(portnum): # ignore duplicates
self.key[0][self.key[0].index(0)] = 1
self.key[3].append(int(portnum))
if not 0 in self.key[0]: # last knock received
self.key[4][1] = thetime # record time
return self.key
if int(portnum) in close_portseq:
if self.key[2] == ip:
self.olog.write("%s Host %s sent close knock: %s\n" % \
(time.asctime(time.localtime()), ip, portnum))
if not 1 in self.key[1]:
self.key[4][0] = thetime
self.key[1][self.key[1].index(0)] = 1
if not 0 in self.key[1]:
self.key[4][1] = thetime
if not self.key[3][-1] == int(portnum):
self.key[3].append(int(portnum))
return self.key
def checkKeys(self):
'''
The meat of this method will not be run unless all
keys in either the close or open knock are recieved
'''
if 0 in self.key[0]:
if 0 in self.key[1]:
return self.key
if not 0 in self.key[0]: # "open" key
if int(self.key[4][1]) - int(self.key[4][0]) < timeout: # check if timeout exceeded
if self.key[3][1:] == open_portseq: # check if knocks in correct order
self.olog.write("%s All open knocks sent by host %s\n" % \
(time.asctime(time.localtime()), self.key[2]))
self.open_port(self.key[2])
self.restart = 1
return
else: # bail out, and reset key
self.olog.write("%s *** Open knocks sent in wrong order by host: %s\n" % \
(time.asctime(time.localtime()), self.key[2]))
self.restart = 1
return
else: # bail out and reset key
self.olog.write("%s *** Timeout by host: %s ***\n" % \
(time.asctime(time.localtime()), self.key[2]))
self.restart = 1
return
if not 0 in self.key[1]: # "close" key
if int(self.key[4][1]) - int(self.key[4][0]) < timeout:
if self.key[3][1:] == close_portseq:
self.olog.write("%s All close knocks sent by host %s\n" % \
(time.asctime(time.localtime()), self.key[2]))
self.close_port()
self.restart = 1
return
else:
self.olog.write("%s *** Close knocks sent in wrong order by host: %s\n" % \
(time.asctime(time.localtime()), self.key[2]))
self.restart = 1
return
else:
self.olog.write("%s *** Timeout by host: %s\n" % \
(time.asctime(time.localtime()), self.key[2]))
self.restart = 1
return
def open_port(self, ipfrom):
command = "%s -A %s -i %s -p tcp -s %s --dport %i -j ACCEPT" % (iptables, chain, ipfrom, iface, openport)
os.system(command)
self.olog.write("%s *** Allowing host %s access to port %i\n" % \
(time.asctime(time.localtime()), ipfrom, openport))
def close_port(self):
command = "%s -A %s -i %s -p tcp -s %s --dport %i -j ACCEPT" % (iptables, chain, ipfrom, iface, openport)
os.system(command)
self.olog.write("%s *** Closing access to port %i\n" % \
(time.asctime(time.localtime()), openport))
def gettime(self, t):
t = t.split(":") # time returned by log (hh:mm:ss)
tt = time.gmtime(time.time()) # current date
ttt = (tt[0], tt[1], tt[2], int(t[0]), int(t[1]), int(t[2]), tt[6], tt[7], tt[8]) # splice them
thetime = time.mktime(ttt) # seconds...
return thetime
if __name__ == '__main__':
olog = open(OUTLOG_FILE, 'a')
olog.write("%s Starting knock.d\n" % \
time.asctime(time.localtime()))
try:
while True:
x = gatekeeper(olog)
x.tailLog()
except KeyboardInterrupt:
olog.write("%s Caught ctrl-c, exiting.\n" % \
time.asctime(time.localtime()))
olog.close()
print"Done"
sys.exit(0)
syntax highlighted by Code2HTML, v. 0.9.1