306 lines
7.9 KiB
Python
Executable File
306 lines
7.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
from datetime import timedelta, datetime
|
|
from tabulate import tabulate
|
|
import psutil
|
|
import random
|
|
import sqlite3
|
|
import time, os, sys
|
|
|
|
def setup_options():
|
|
''' Setup the command line options '''
|
|
from argparse import ArgumentParser
|
|
|
|
parser=ArgumentParser(description="Alive notifier.")
|
|
parser.add_argument(
|
|
"--db",
|
|
action = 'store',
|
|
dest = 'DB',
|
|
default = '/tmp/ssh-backdoor.sqlite',
|
|
type = str,
|
|
help = "Sqlite file for database: %(default)s"
|
|
)
|
|
# TODO --connect (replace ssh-local-connect)
|
|
parser.add_argument(
|
|
"--clear",
|
|
action = 'store_true',
|
|
dest = 'clear',
|
|
default = False,
|
|
help = "Clear the database of everything, first"
|
|
)
|
|
parser.add_argument(
|
|
"--kill",
|
|
action = 'store',
|
|
dest = 'kill',
|
|
default = None,
|
|
type = str,
|
|
help="Kill processes of given ID"
|
|
)
|
|
parser.add_argument(
|
|
"--list","-l",
|
|
action = 'store_true',
|
|
dest = 'list',
|
|
default = False,
|
|
help = "List ports"
|
|
)
|
|
parser.add_argument(
|
|
"--list-names",
|
|
action = 'store_true',
|
|
dest = 'list_names',
|
|
default = False,
|
|
help = "List alive host names only"
|
|
)
|
|
parser.add_argument(
|
|
"--query",
|
|
action = 'store',
|
|
dest = 'query',
|
|
default = None,
|
|
type = str,
|
|
help="Query port for an id"
|
|
)
|
|
parser.add_argument(
|
|
"--quiet", "-q",
|
|
action = 'store_true',
|
|
dest = 'quiet',
|
|
default = False,
|
|
help = "Quiet operation: %(default)s"
|
|
)
|
|
parser.add_argument(
|
|
"--wait", "-w",
|
|
action = 'store_true',
|
|
dest = 'wait',
|
|
default = False,
|
|
help = "Enter infinite loop"
|
|
)
|
|
parser.add_argument(
|
|
action = 'store',
|
|
dest = 'id',
|
|
default = None ,
|
|
type = str,
|
|
nargs = '?',
|
|
help="Id name for port"
|
|
)
|
|
options=parser.parse_args()
|
|
return options
|
|
|
|
|
|
def eprint(s):
|
|
ewrite(str(s) + "\n")
|
|
|
|
|
|
def ewrite(s):
|
|
sys.stderr.write(str(s))
|
|
sys.stderr.flush()
|
|
|
|
|
|
class DataBase:
|
|
def __init__(self, DB):
|
|
self.alive = 7 * 24 * 3600 # 7 days
|
|
self.DBfile = DB
|
|
self.conn = None
|
|
self.db = None
|
|
self.id = None
|
|
self.to_die = False
|
|
if not os.path.exists(self.DBfile):
|
|
self.createDB()
|
|
self.conn_init()
|
|
self.clean_db()
|
|
|
|
def clear(self):
|
|
self.db = self.conn.cursor()
|
|
self.db.execute("DELETE FROM ports")
|
|
self.conn_end()
|
|
|
|
def conn_init(self):
|
|
self.conn = sqlite3.connect(self.DBfile)
|
|
self.db = self.conn.cursor()
|
|
self.conn.text_factory = str
|
|
|
|
def conn_end(self):
|
|
self.conn.commit()
|
|
#self.conn.close()
|
|
|
|
def createDB(self):
|
|
self.conn_init()
|
|
self.db.execute('CREATE TABLE ports (\
|
|
id TEXT PRIMARY KEY,\
|
|
host TEXT, \
|
|
port INTEGER,\
|
|
pid INTEGER,\
|
|
die INTEGER,\
|
|
date INTEGER)')
|
|
self.conn_end()
|
|
|
|
def clean_db(self):
|
|
self.db = self.conn.cursor()
|
|
self.db.execute("SELECT id,port,date,pid,host FROM ports")
|
|
for row in self.db.fetchall():
|
|
age = int(time.time() - row[2])
|
|
running = self.is_running(row[3])
|
|
if running:
|
|
continue
|
|
if age < self.alive:
|
|
continue
|
|
self.db.execute("DELETE FROM ports WHERE id = ?", (row[0],))
|
|
self.conn_end()
|
|
|
|
|
|
def die(self):
|
|
eprint("\n\nDying by request\n\n")
|
|
parent = self.get_pid()
|
|
if parent > 1:
|
|
try:
|
|
os.kill(parent, 9)
|
|
except OSError:
|
|
pass
|
|
sys.exit(1)
|
|
|
|
|
|
def check_die(self):
|
|
""" kills process, and parents, if set to die """
|
|
if self.id == None:
|
|
return
|
|
self.db = self.conn.cursor()
|
|
self.db.execute("SELECT id,pid,die FROM ports WHERE id = ?", (self.id,))
|
|
result = self.db.fetchall()
|
|
if len(result) == 0:
|
|
return None
|
|
to_die = result[0][2]
|
|
# if parent process is 1, then ssh connection has died
|
|
self.to_die = self.to_die or to_die == 1 or result[0][1] == 1
|
|
if self.to_die:
|
|
self.die()
|
|
|
|
|
|
def set_to_die(self, id):
|
|
""" set to die """
|
|
|
|
self.db = self.conn.cursor()
|
|
self.db.execute("SELECT id FROM ports WHERE id = ?", (id,))
|
|
result = self.db.fetchall()
|
|
if len(result) == 0:
|
|
eprint("No such ID found")
|
|
return None
|
|
self.db.execute("UPDATE ports SET die = 1 WHERE id = ?",
|
|
(id,)
|
|
)
|
|
self.conn_end()
|
|
|
|
|
|
def update(self, id):
|
|
self.id = id
|
|
port = self.get_port(id)
|
|
if port == None:
|
|
port = self.new_port()
|
|
parent = self.get_pid()
|
|
self.db = self.conn.cursor()
|
|
self.db.execute("INSERT OR REPLACE INTO ports(id,port,date,pid,host,die) \
|
|
VALUES(?,?,?,?,?,?)",(
|
|
id,
|
|
port,
|
|
int(time.time()),
|
|
parent,
|
|
os.getenv("SSH_CLIENT","-").split(" ")[0],
|
|
self.to_die
|
|
)
|
|
)
|
|
self.conn_end()
|
|
return port
|
|
|
|
def list(self):
|
|
self.db = self.conn.cursor()
|
|
self.db.execute("SELECT id,port,host,date,pid FROM ports ORDER BY id")
|
|
rows = []
|
|
for row in self.db:
|
|
row = list(row)
|
|
row[3] = self.human_age(row[3])
|
|
row.append(self.is_running(row[4]))
|
|
# ~ row.append("ssh-nosave -p %d localhost"%( row[1], ) )
|
|
rows.append(row)
|
|
return rows
|
|
|
|
|
|
def human_age(self, sec):
|
|
sec = timedelta(seconds = time.time() - sec)
|
|
d = datetime(1,1,1) + sec
|
|
return "%d %02d:%02d:%02d" % (d.day - 1, d.hour, d.minute, d.second)
|
|
|
|
|
|
def is_running(self, pid):
|
|
try:
|
|
os.kill(pid, 0)
|
|
except OSError:
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def get_pid(self):
|
|
return os.getppid()
|
|
|
|
def get_port(self, id):
|
|
self.db = self.conn.cursor()
|
|
self.db.execute("SELECT port FROM ports WHERE id = ?", (id,))
|
|
result = self.db.fetchall()
|
|
if len(result) == 0:
|
|
return None
|
|
return result[0][0]
|
|
|
|
def new_port(self):
|
|
self.db = self.conn.cursor()
|
|
while True:
|
|
port = random.randint(22200, 22400)
|
|
self.db.execute("SELECT port FROM ports WHERE port = ?", (port,))
|
|
result = self.db.fetchall()
|
|
if len(result) == 0:
|
|
return port
|
|
|
|
if __name__ == "__main__":
|
|
opts=setup_options()
|
|
db = DataBase(opts.DB)
|
|
start_time = time.time()
|
|
|
|
if opts.clear:
|
|
db.clear()
|
|
|
|
if opts.list_names:
|
|
for row in db.list():
|
|
if row[5]:
|
|
print(row[0])
|
|
sys.exit(0)
|
|
|
|
if opts.list:
|
|
print(tabulate(
|
|
db.list(),
|
|
headers = ['Id','Port','Host','Age','PID','Alive']
|
|
))
|
|
|
|
if opts.query != None:
|
|
port = db.get_port(opts.query)
|
|
if port == None:
|
|
sys.exit(1)
|
|
print(port)
|
|
sys.exit(0)
|
|
|
|
if opts.kill:
|
|
db.set_to_die(opts.kill)
|
|
sys.exit(0)
|
|
|
|
if opts.id != None:
|
|
print(db.update(opts.id))
|
|
if opts.wait:
|
|
ewrite(
|
|
" Connected\r"
|
|
)
|
|
while True:
|
|
if time.time() - start_time > 3600:
|
|
sys.exit(0)
|
|
db.check_die()
|
|
db.update(opts.id)
|
|
for i in range(10):
|
|
time.sleep(1)#0 * random.random())
|
|
ewrite(
|
|
" " +
|
|
time.strftime("%c") +
|
|
"\r"
|
|
)
|