#!/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" )