solve a few bug

This commit is contained in:
Ghislain Picard 2021-01-22 23:52:22 +01:00
commit b5b6d282fb

View file

@ -1,4 +1,4 @@
#!/usr/bin/python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Ereshkigal is an AutoSSH tunnel monitor # Ereshkigal is an AutoSSH tunnel monitor
@ -24,6 +24,9 @@
# CORE # CORE
################################################################################################# #################################################################################################
import signal
import time
import curses
import os import os
import subprocess import subprocess
import logging import logging
@ -31,19 +34,20 @@ import psutil
import socket import socket
import re import re
import collections import collections
import itertools
class Tunnel: class Tunnel:
def __init__(self, ssh_pid = None, in_port = None, via_host = None, target_host = None, out_port = None): def __init__(self, ssh_pid=None, in_port=None, via_host=None, target_host=None, out_port=None):
# assert(ssh_pid != None) # assert ssh_pid is not None
self.ssh_pid = ssh_pid self.ssh_pid = ssh_pid
assert(in_port!=None) assert in_port is not None
self.in_port = in_port self.in_port = in_port
assert(via_host!=None) assert via_host is not None
self.via_host = via_host self.via_host = via_host
assert(target_host!=None) assert target_host is not None
self.target_host = target_host self.target_host = target_host
assert(out_port!=None) assert out_port is not None
self.out_port = out_port self.out_port = out_port
self.connections = [] self.connections = []
@ -68,9 +72,9 @@ class Tunnel:
class AutoTunnel(Tunnel): class AutoTunnel(Tunnel):
def __init__(self, autossh_pid = None, *args, **kwargs): def __init__(self, autossh_pid=None, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
assert(autossh_pid!=None) assert autossh_pid is not None
self.autossh_pid = autossh_pid self.autossh_pid = autossh_pid
def repr_tunnel(self): def repr_tunnel(self):
@ -90,22 +94,22 @@ class RawTunnel(Tunnel):
class Connection: class Connection:
"""A dictionary that stores an SSH connection related to a tunnel""" """A dictionary that stores an SSH connection related to a tunnel"""
def __init__(self, local_address = None, in_port = None, foreign_address = None, out_port = None, def __init__(self, local_address=None, in_port=None, foreign_address=None, out_port=None,
status = None, family = None ): status=None, family=None):
# informations available with netstat # informations available with netstat
assert(local_address!=None) assert local_address is not None
self.local_address = local_address self.local_address = local_address
assert(in_port!=None) assert in_port is not None
self.in_port = in_port self.in_port = in_port
self.foreign_address = foreign_address self.foreign_address = foreign_address
self.out_port = out_port self.out_port = out_port
assert(status!=None) assert status is not None
self.status = status self.status = status
assert(family!=None) assert family is not None
self.family = family self.family = family
self.family_rep = {socket.AddressFamily.AF_INET:"INET", socket.AddressFamily.AF_INET6:"INET6", socket.AddressFamily.AF_UNIX:"UNIX"} self.family_rep = {socket.AddressFamily.AF_INET: "INET", socket.AddressFamily.AF_INET6: "INET6", socket.AddressFamily.AF_UNIX: "UNIX"}
# FIXME would be nice to have an estimation of the connections latency # FIXME would be nice to have an estimation of the connections latency
#self.latency = 0 #self.latency = 0
@ -120,14 +124,14 @@ class Connection:
self.in_port, self.in_port,
self.foreign_address, self.foreign_address,
self.out_port, self.out_port,
) )
else: else:
return "%s\t%s\t%s:%i" % ( return "%s\t%s\t%s:%i" % (
self.family_rep[self.family], self.family_rep[self.family],
self.status, self.status,
self.local_address, self.local_address,
self.in_port, self.in_port,
) )
class TunnelsParser: class TunnelsParser:
@ -140,41 +144,51 @@ class TunnelsParser:
# do not perform update by default # do not perform update by default
# this is necessary because one may want # this is necessary because one may want
# only a list of connections OR autossh processes # only a list of connections OR autossh processes
#self.update() # self.update()
self.re_forwarding = re.compile(r"-L(\d+):(.+):(\d+)") self.re_forwarding = re.compile(r"-L\s*(\d+):(.*):(\d+)")
self.header = 'TYPE\tSSH_PID\tIN_PORT\tVIA_HOST\tTARGET_HOST\tOUT_PORT' self.header = 'TYPE\tSSH_PID\tIN_PORT\tVIA_HOST\tTARGET_HOST\tOUT_PORT'
def get_tunnel(self, pos): def get_tunnel(self, pos):
pid = list(self.tunnels.keys())[pos] pid = list(self.tunnels.keys())[pos]
return self.tunnels[pid] return self.tunnels[pid]
def parse(self, cmd): def parse(self, cmd):
cmdline = " ".join(cmd) cmdline = " ".join(cmd)
logging.debug('autossh cmd line:', cmdline) logging.debug('autossh cmd line: %s', cmdline)
logging.debug('forwarding regexp:', self.re_forwarding) logging.debug('forwarding regexp: %s', self.re_forwarding)
match = self.re_forwarding.findall(cmdline) match = self.re_forwarding.findall(cmdline)
logging.debug(match) logging.debug(match)
if match: if match:
assert(len(match)==1) assert len(match) == 1
in_port, target_host, out_port = match[0] in_port, target_host, out_port = match[0]
logging.debug("matches: ", match) logging.debug("matches: %s", match)
else:
raise ValueError("is not a ssh tunnel")
# Find the hostname on wich the tunnel is built. # Find the hostname on wich the tunnel is built.
via_host = "unknown" via_host = "unknown"
# Search backward and take the first parameter argument. # Search backward and take the first parameter argument.
# FIXME this is an ugly hack # FIXME this is an ugly hack
for i in range( len(cmd)-1,0,-1 ): i = 1
if cmd[i][0] != '-': while i < len(cmd):
logging.debug("ici: %i %s", i, cmd[i])
if cmd[i][0] == '-':
if cmd[i][1] in '46AaCfGgKkMNnqsTtVvXxYy':
# flag without argument
pass
elif len(cmd[i]) == 2: # the argument is likely the next one
if (i < len(cmd) - 1) and (cmd[i + 1][0] != '-'): # not another flag (this should always be true)
i += 1 # skip the argument
# skip the argument
i += 1
else:
via_host = cmd[i] via_host = cmd[i]
break break
return (int(in_port), via_host, target_host, int(out_port)) return int(in_port), via_host, target_host, int(out_port)
def update(self): def update(self):
"""Gather and parse informations from the operating system""" """Gather and parse informations from the operating system"""
@ -184,41 +198,43 @@ class TunnelsParser:
# Browse the SSH processes handling a tunnel. # Browse the SSH processes handling a tunnel.
for proc in psutil.process_iter(): for proc in psutil.process_iter():
try: try:
process = proc.as_dict(attrs=['pid','ppid','name','cmdline','connections']) process = proc.as_dict(attrs=['pid', 'ppid', 'name', 'cmdline', 'connections'])
cmd = process['cmdline'] cmd = process['cmdline']
except psutil.NoSuchProcess: except psutil.NoSuchProcess:
pass pass
else: else:
if process['name'] == 'ssh': if process['name'] == 'ssh':
logging.debug(process) logging.debug(process)
in_port, via_host, target_host, out_port = self.parse(cmd) try:
logging.debug(in_port, via_host, target_host, out_port) in_port, via_host, target_host, out_port = self.parse(cmd)
except ValueError:
continue
logging.debug("%s %s %s %s", in_port, via_host, target_host, out_port)
# Check if this ssh tunnel is managed by autossh. # Check if this ssh tunnel is managed by autossh.
parent = psutil.Process(process['ppid']) parent = psutil.Process(process['ppid'])
if parent.name() == 'autossh': if parent.name() == 'autossh':
# Add an autossh tunnel. # Add an autossh tunnel.
pid = parent.pid # autossh pid pid = parent.pid # autossh pid
self.tunnels[pid] = AutoTunnel(pid, process['pid'], in_port, via_host, target_host, out_port ) self.tunnels[pid] = AutoTunnel(pid, process['pid'], in_port, via_host, target_host, out_port)
else: else:
# Add a raw tunnel. # Add a raw tunnel.
pid = process['pid'] pid = process['pid']
self.tunnels[pid] = RawTunnel(pid, in_port, via_host, target_host, out_port ) self.tunnels[pid] = RawTunnel(pid, in_port, via_host, target_host, out_port)
for c in process['connections']: for c in process['connections']:
logging.debug(c) logging.debug(c)
laddr,lport = c.laddr laddr, lport = c.laddr
if c.raddr: if c.raddr:
raddr,rport = c.raddr raddr, rport = c.raddr
else: else:
raddr,rport = (None,None) raddr, rport = (None, None)
connection = Connection(laddr,lport,raddr,rport,c.status,c.family) connection = Connection(laddr, lport, raddr, rport, c.status, c.family)
logging.debug(connection) logging.debug(connection)
self.tunnels[pid].connections.append(connection) self.tunnels[pid].connections.append(connection)
logging.debug(self.tunnels) logging.debug(self.tunnels)
def __repr__(self): def __repr__(self):
reps = [self.header] reps = [self.header]
for t in self.tunnels: for t in self.tunnels:
@ -226,14 +242,10 @@ class TunnelsParser:
return "\n".join(reps) return "\n".join(reps)
################################################################################################# #################################################################################################
# INTERFACES # INTERFACES
################################################################################################# #################################################################################################
import curses
import time
import signal
class CursesMonitor: class CursesMonitor:
"""Textual user interface to display up-to-date informations about current tunnels""" """Textual user interface to display up-to-date informations about current tunnels"""
@ -255,16 +267,19 @@ class CursesMonitor:
self.show_connections = False self.show_connections = False
# FIXME pass as parameters+options # FIXME pass as parameters+options
self.update_delay = 1 # seconds of delay between two data updates self.update_delay = 1 # seconds of delay between two data updates
self.ui_delay = 0.05 # seconds between two screen update self.ui_delay = 0.05 # seconds between two screen update
# colors # colors
# FIXME different colors for different types of tunnels (auto or raw) # FIXME different colors for different types of tunnels (auto or raw)
self.colors_tunnel = {'kind_auto':4, 'kind_raw':5, 'ssh_pid':0, 'in_port':3, 'via_host':2, 'target_host':2, 'out_port':3, 'tunnels_nb':4, 'tunnels_nb_none':1} self.colors_tunnel = {'kind_auto': 4, 'kind_raw': 5, 'ssh_pid': 0, 'in_port': 3,
self.colors_highlight = {'kind_auto':9, 'kind_raw':9, 'ssh_pid':9, 'in_port':9, 'via_host':9, 'target_host':9, 'out_port':9, 'tunnels_nb':9, 'tunnels_nb_none':9} 'via_host': 2, 'target_host': 2, 'out_port': 3, 'tunnels_nb': 4, 'tunnels_nb_none': 1}
self.colors_connection = {'ssh_pid':0, 'autossh_pid':0, 'status':4, 'status_out':1, 'local_address':2, 'in_port':3, 'foreign_address':2, 'out_port':3} self.colors_highlight = {'kind_auto': 9, 'kind_raw': 9, 'ssh_pid': 9, 'in_port': 9,
'via_host': 9, 'target_host': 9, 'out_port': 9, 'tunnels_nb': 9, 'tunnels_nb_none': 9}
self.colors_connection = {'ssh_pid': 0, 'autossh_pid': 0, 'status': 4, 'status_out': 1,
'local_address': 2, 'in_port': 3, 'foreign_address': 2, 'out_port': 3}
self.header = ("TYPE","SSHPID","INPORT","VIA","TARGET","OUTPORT") self.header = ("TYPE", "SSHPID", "INPORT", "VIA", "TARGET", "OUTPORT")
def do_Q(self): def do_Q(self):
"""Quit""" """Quit"""
@ -273,7 +288,6 @@ class CursesMonitor:
logging.debug("Key pushed: Q") logging.debug("Key pushed: Q")
return False return False
def do_R(self): def do_R(self):
"""Reload autossh tunnel""" """Reload autossh tunnel"""
logging.debug("Waited: %s" % self.log_ticks) logging.debug("Waited: %s" % self.log_ticks)
@ -285,12 +299,11 @@ class CursesMonitor:
if type(self.tp.get_tunnel(self.cur_line)) == AutoTunnel: if type(self.tp.get_tunnel(self.cur_line)) == AutoTunnel:
# autossh performs a reload of existing tunnels that it manages # autossh performs a reload of existing tunnels that it manages
logging.debug("SIGUSR1 on PID: %i" % self.cur_pid) logging.debug("SIGUSR1 on PID: %i" % self.cur_pid)
os.kill( self.cur_pid, signal.SIGUSR1 ) os.kill(self.cur_pid, signal.SIGUSR1)
else: else:
logging.debug("Cannot reload a RAW tunnel") logging.debug("Cannot reload a RAW tunnel")
return True return True
def do_C(self): def do_C(self):
"""Close tunnel""" """Close tunnel"""
logging.debug("Waited: %s" % self.log_ticks) logging.debug("Waited: %s" % self.log_ticks)
@ -305,21 +318,20 @@ class CursesMonitor:
if type(tunnel) == AutoTunnel: if type(tunnel) == AutoTunnel:
logging.debug("SIGKILL on autossh PID: %i" % self.cur_pid) logging.debug("SIGKILL on autossh PID: %i" % self.cur_pid)
try: try:
os.kill( self.cur_pid, signal.SIGKILL ) os.kill(self.cur_pid, signal.SIGKILL)
except OSError: except OSError:
logging.error("No such process: %i" % self.cur_pid) logging.error("No such process: %i" % self.cur_pid)
logging.debug("SIGKILL on ssh PID: %i" % tunnel.ssh_pid) logging.debug("SIGKILL on ssh PID: %i" % tunnel.ssh_pid)
try: try:
os.kill( tunnel.ssh_pid, signal.SIGKILL ) os.kill(tunnel.ssh_pid, signal.SIGKILL)
except OSError: except OSError:
logging.error("No such process: %i" % tunnel.ssh_pid) logging.error("No such process: %i" % tunnel.ssh_pid)
self.cur_line = -1 self.cur_line -= 1
self.cur_pid = -1 self.cur_pid = -1
# FIXME update cur_pid or get rid of it everywhere # FIXME update cur_pid or get rid of it everywhere
return True return True
def do_N(self): def do_N(self):
"""Show connections""" """Show connections"""
logging.debug("Waited: %s" % self.log_ticks) logging.debug("Waited: %s" % self.log_ticks)
@ -328,7 +340,6 @@ class CursesMonitor:
self.show_connections = not self.show_connections self.show_connections = not self.show_connections
return True return True
def do_258(self): def do_258(self):
"""Move down""" """Move down"""
logging.debug("Waited: %s" % self.log_ticks) logging.debug("Waited: %s" % self.log_ticks)
@ -344,7 +355,6 @@ class CursesMonitor:
self.cur_pid = self.tp.get_tunnel(self.cur_line).ssh_pid self.cur_pid = self.tp.get_tunnel(self.cur_line).ssh_pid
return True return True
def do_259(self): def do_259(self):
"""Move up""" """Move up"""
logging.debug("Waited: %s" % self.log_ticks) logging.debug("Waited: %s" % self.log_ticks)
@ -353,15 +363,14 @@ class CursesMonitor:
if self.cur_line > -1: if self.cur_line > -1:
self.cur_line -= 1 self.cur_line -= 1
if self.cur_line > 0: if self.cur_line > 0:
self.cur_pid = self.tp.get_tunnel(self.cur_line).pid self.cur_pid = self.tp.get_tunnel(self.cur_line).ssh_pid
return True return True
def __call__(self): def __call__(self):
"""Start the interface""" """Start the interface"""
self.scr.clear() # clear all self.scr.clear() # clear all
self.scr.nodelay(1) # non-bloking getch self.scr.nodelay(1) # non-bloking getch
# first display # first display
self.display() self.display()
@ -377,7 +386,7 @@ class CursesMonitor:
# wait some time # wait some time
# necessary to not overload the system with unnecessary calls # necessary to not overload the system with unnecessary calls
time.sleep( self.ui_delay ) time.sleep(self.ui_delay)
# if its time to update # if its time to update
if time.time() > self.last_update + self.update_delay: if time.time() > self.last_update + self.update_delay:
@ -395,22 +404,21 @@ class CursesMonitor:
else: else:
self.log_ticks += "." self.log_ticks += "."
kc = self.scr.getch() # keycode
kc = self.scr.getch() # keycode if kc != -1: # if keypress
if kc != -1: # if keypress
pass pass
ch = chr(0) ch = chr(0)
if 0 < kc < 256: # if ascii key if 0 < kc < 256: # if ascii key
# ascii character from the keycode # ascii character from the keycode
ch = chr(kc) ch = chr(kc)
# Call the do_* handler. # Call the do_* handler.
fch = "do_%s" % ch.capitalize() fch = "do_%s" % ch.capitalize()
fkc = "do_%i" % kc fkc = "do_%i" % kc
logging.debug("key func: %s / %s" % (fch,fkc)) logging.debug("key func: %s / %s" % (fch, fkc))
if fch in dir(self): if fch in dir(self):
notquit = eval("self."+fch+"()") notquit = eval("self."+fch+"()")
elif fkc in dir(self): elif fkc in dir(self):
@ -425,20 +433,17 @@ class CursesMonitor:
# end of the loop # end of the loop
def format(self): def format(self):
reps = [self.tp.tunnels[t].repr_tunnel() for t in self.tp.tunnels] reps = [self.tp.tunnels[t].repr_tunnel() for t in self.tp.tunnels]
tuns = [t.split() for t in reps] tuns = [t.split() for t in reps]
tuns.append(self.header) tuns.append(self.header)
logging.debug(tuns) cols = itertools.zip_longest(*tuns, fillvalue='')
cols = zip(*tuns)
widths = [max(len(s) for s in col) for col in cols] widths = [max(len(s) for s in col) for col in cols]
logging.debug(widths) logging.debug(widths)
fmt = ['{{: <{}}}'.format(w) for w in widths] fmt = ['{{: <{}}}'.format(w) for w in widths]
logging.debug(fmt) logging.debug(fmt)
return fmt return fmt
def display(self): def display(self):
"""Generate the interface screen""" """Generate the interface screen"""
@ -446,27 +451,27 @@ class CursesMonitor:
h = [] h = []
for f in dir(self): for f in dir(self):
if "do_" in f: if "do_" in f:
key = f.replace("do_","") key = f.replace("do_", "")
if key.isalpha(): # We do not want arrows. if key.isalpha(): # We do not want arrows.
msg = "[%s] %s" % (key,eval("self.%s.__doc__" % f)) msg = "[%s] %s" % (key, eval("self.%s.__doc__" % f))
h.append(msg) h.append(msg)
help_msg = ", ".join(h) help_msg = ", ".join(h)
help_msg += "\n" help_msg += "\n"
self.scr.addstr(0,0, help_msg, curses.color_pair(4) ) self.scr.addstr(0, 0, help_msg, curses.color_pair(4))
self.scr.clrtoeol() self.scr.clrtoeol()
# Second line # Second line
self.scr.addstr( "Active tunnels: ", curses.color_pair(6) ) self.scr.addstr("Active tunnels: ", curses.color_pair(6))
self.scr.addstr( str( len(self.tp.tunnels) ), curses.color_pair(1) ) self.scr.addstr(str(len(self.tp.tunnels)), curses.color_pair(1))
self.scr.addstr( " / Active connections: ", curses.color_pair(6) ) self.scr.addstr(" / Active connections: ", curses.color_pair(6))
self.scr.addstr( str( sum([len(self.tp.tunnels[t].connections) for t in self.tp.tunnels]) ), curses.color_pair(1) ) self.scr.addstr(str(sum([len(self.tp.tunnels[t].connections) for t in self.tp.tunnels])), curses.color_pair(1))
self.scr.addstr( '\n', curses.color_pair(1) ) self.scr.addstr('\n', curses.color_pair(1))
self.scr.clrtoeol() self.scr.clrtoeol()
# if no line is selected # if no line is selected
color = 0 color = 0
if self.cur_line==-1: if self.cur_line == -1:
# selected color for the header # selected color for the header
color = 9 color = 9
self.cur_pid = -1 self.cur_pid = -1
@ -476,31 +481,30 @@ class CursesMonitor:
# if os.geteuid() == 0: # if os.geteuid() == 0:
header_msg = " ".join(self.format()).format(*self.header) header_msg = " ".join(self.format()).format(*self.header)
header_msg += " CONNECTIONS" header_msg += " CONNECTIONS"
self.scr.addstr( header_msg, curses.color_pair(color) ) self.scr.addstr(header_msg, curses.color_pair(color))
self.scr.clrtoeol() self.scr.clrtoeol()
# for each tunnel processes available in the monitor # for each tunnel processes available in the monitor
for l in range(len(self.tp.tunnels)): for l in range(len(self.tp.tunnels)):
# add a line for the l-th autossh process # add a line for the l-th autossh process
self.add_tunnel( l ) self.add_tunnel(l)
# if one want to show connections # if one want to show connections
if self.show_connections:# and os.getuid() == 0: if self.show_connections: # and os.getuid() == 0:
self.add_connection( l ) self.add_connection(l)
self.scr.clrtobot() self.scr.clrtobot()
def add_connection(self, line):
def add_connection(self, line ):
"""Add lines for each connections related to the l-th autossh process""" """Add lines for each connections related to the l-th autossh process"""
colors = self.colors_connection colors = self.colors_connection
# for each connections related to te line-th autossh process # for each connections related to te line-th autossh process
for t in sorted(self.tp.get_tunnel(line).connections, key=lambda c:c.status): for t in sorted(self.tp.get_tunnel(line).connections, key=lambda c: c.status):
# FIXME fail if the screen's height is too small. # FIXME fail if the screen's height is too small.
self.scr.addstr( '\n\t+ ' ) self.scr.addstr('\n\t+ ')
color = self.colors_connection['status'] color = self.colors_connection['status']
# if the connections is established # if the connections is established
@ -508,38 +512,37 @@ class CursesMonitor:
if t.status != 'ESTABLISHED' and t.status != 'LISTEN': if t.status != 'ESTABLISHED' and t.status != 'LISTEN':
color = self.colors_connection['status_out'] color = self.colors_connection['status_out']
self.scr.addstr( t.status, curses.color_pair( color ) ) self.scr.addstr(t.status, curses.color_pair(color))
self.scr.addstr( '\t' ) self.scr.addstr('\t')
# self.scr.addstr( str( t['ssh_pid'] ), curses.color_pair(colors['ssh_pid'] ) ) # self.scr.addstr( str( t['ssh_pid'] ), curses.color_pair(colors['ssh_pid'] ) )
# self.scr.addstr( '\t' ) # self.scr.addstr( '\t' )
self.scr.addstr( str( t.local_address ) , curses.color_pair(colors['local_address'] )) self.scr.addstr(str(t.local_address), curses.color_pair(colors['local_address']))
self.scr.addstr( ':' ) self.scr.addstr(':')
self.scr.addstr( str( t.in_port ) , curses.color_pair(colors['in_port'] )) self.scr.addstr(str(t.in_port), curses.color_pair(colors['in_port']))
if t.foreign_address and t.out_port: if t.foreign_address and t.out_port:
self.scr.addstr( ' -> ' ) self.scr.addstr(' -> ')
self.scr.addstr( str( t.foreign_address ) , curses.color_pair(colors['foreign_address'] )) self.scr.addstr(str(t.foreign_address), curses.color_pair(colors['foreign_address']))
self.scr.addstr( ':' ) self.scr.addstr(':')
self.scr.addstr( str( t.out_port ) , curses.color_pair(colors['out_port'] )) self.scr.addstr(str(t.out_port), curses.color_pair(colors['out_port']))
self.scr.clrtoeol() self.scr.clrtoeol()
def add_tunnel(self, line): def add_tunnel(self, line):
"""Add line corresponding to the line-th autossh process""" """Add line corresponding to the line-th autossh process"""
self.scr.addstr( '\n' ) self.scr.addstr('\n')
colors = self.colors_tunnel colors = self.colors_tunnel
if self.cur_line == line: if self.cur_line == line:
colors = self.colors_highlight colors = self.colors_highlight
if type(self.tp.get_tunnel(line)) == AutoTunnel: if type(self.tp.get_tunnel(line)) == AutoTunnel:
self.scr.addstr( self.format()[0].format('auto'), curses.color_pair(colors['kind_auto']) ) self.scr.addstr(self.format()[0].format('auto'), curses.color_pair(colors['kind_auto']))
self.scr.addstr( ' ', curses.color_pair(colors['kind_auto']) ) self.scr.addstr(' ', curses.color_pair(colors['kind_auto']))
else: else:
self.scr.addstr( self.format()[0].format('ssh'), curses.color_pair(colors['kind_raw']) ) self.scr.addstr(self.format()[0].format('ssh'), curses.color_pair(colors['kind_raw']))
self.scr.addstr( ' ', curses.color_pair(colors['kind_raw']) ) self.scr.addstr(' ', curses.color_pair(colors['kind_raw']))
# self.add_tunnel_info('ssh_pid', line) # self.add_tunnel_info('ssh_pid', line)
self.add_tunnel_info('ssh_pid', line, 1) self.add_tunnel_info('ssh_pid', line, 1)
@ -548,41 +551,39 @@ class CursesMonitor:
self.add_tunnel_info('target_host', line, 4) self.add_tunnel_info('target_host', line, 4)
self.add_tunnel_info('out_port', line, 5) self.add_tunnel_info('out_port', line, 5)
nb = len(self.tp.get_tunnel(line).connections ) nb = len(self.tp.get_tunnel(line).connections)
if nb > 0: if nb > 0:
# for each connection related to this process # for each connection related to this process
for i in self.tp.get_tunnel(line).connections: for i in self.tp.get_tunnel(line).connections:
# add a vertical bar | # add a vertical bar |
# the color change according to the status of the connection # the color change according to the status of the connection
if i.status == 'ESTABLISHED' or i.status == 'LISTEN': if i.status == 'ESTABLISHED' or i.status == 'LISTEN':
self.scr.addstr( '|', curses.color_pair(self.colors_connection['status']) ) self.scr.addstr('|', curses.color_pair(self.colors_connection['status']))
else: else:
self.scr.addstr( '|', curses.color_pair(self.colors_connection['status_out']) ) self.scr.addstr('|', curses.color_pair(self.colors_connection['status_out']))
else: else:
# if os.geteuid() == 0: # if os.geteuid() == 0:
# if there is no connection, display a "None" # if there is no connection, display a "None"
self.scr.addstr( 'None', curses.color_pair(self.colors_tunnel['tunnels_nb_none']) ) self.scr.addstr('None', curses.color_pair(self.colors_tunnel['tunnels_nb_none']))
self.scr.clrtoeol() self.scr.clrtoeol()
def add_tunnel_info(self, key, line, col):
def add_tunnel_info( self, key, line, col ):
"""Add an information of an autossh process, in the configured color""" """Add an information of an autossh process, in the configured color"""
colors = self.colors_tunnel colors = self.colors_tunnel
# if the line is selected # if the line is selected
if self.cur_line == line: if self.cur_line == line:
# set the color to the highlight one # set the color to the highlight one
colors = self.colors_highlight colors = self.colors_highlight
txt = eval("str(self.tp.get_tunnel(line).%s)" % key) txt = eval("str(self.tp.get_tunnel(line).%s)" % key)
if key == 'target_host' or key == 'via_host': if key == 'target_host' or key == 'via_host':
txt = eval("str(self.tp.get_tunnel(line).%s)" % key) txt = eval("str(self.tp.get_tunnel(line).%s)" % key)
self.scr.addstr(self.format()[col].format(txt), curses.color_pair(colors[key]) ) self.scr.addstr(self.format()[col].format(txt), curses.color_pair(colors[key]))
self.scr.addstr( ' ', curses.color_pair(colors[key]) ) self.scr.addstr(' ', curses.color_pair(colors[key]))
if __name__ == "__main__": if __name__ == "__main__":
@ -598,30 +599,30 @@ if __name__ == "__main__":
parser = OptionParser(usage=usage) parser = OptionParser(usage=usage)
parser.add_option("-c", "--curses", parser.add_option("-c", "--curses",
action="store_true", default=False, action="store_true", default=False,
help="Start the user interface in text mode.") help="Start the user interface in text mode.")
parser.add_option("-n", "--connections", parser.add_option("-n", "--connections",
action="store_true", default=False, action="store_true", default=False,
help="Display only SSH connections related to a tunnel.") help="Display only SSH connections related to a tunnel.")
parser.add_option("-u", "--tunnels", parser.add_option("-u", "--tunnels",
action="store_true", default=False, action="store_true", default=False,
help="Display only the list of tunnels processes.") help="Display only the list of tunnels processes.")
LOG_LEVELS = {'error' : logging.ERROR, LOG_LEVELS = {'error': logging.ERROR,
'warning' : logging.WARNING, 'warning': logging.WARNING,
'debug' : logging.DEBUG} 'debug': logging.DEBUG}
parser.add_option('-l', '--log-level', choices=list(LOG_LEVELS), default='error', metavar='LEVEL', parser.add_option('-l', '--log-level', choices=list(LOG_LEVELS), default='error', metavar='LEVEL',
help='Log level (%s), default: %s.' % (", ".join(LOG_LEVELS), 'error') ) help='Log level (%s), default: %s.' % (", ".join(LOG_LEVELS), 'error'))
parser.add_option('-g', '--log-file', default=None, metavar='FILE', parser.add_option('-g', '--log-file', default=None, metavar='FILE',
help="Log to this file, default to standard output. \ help="Log to this file, default to standard output. \
If you use the curses interface, you may want to set this to actually see logs.") If you use the curses interface, you may want to set this to actually see logs.")
parser.add_option('-f', '--config-file', default=None, metavar='FILE', parser.add_option('-f', '--config-file', default=None, metavar='FILE',
help="Use this configuration file (default: '~/.ereshkigal.conf')") help="Use this configuration file (default: '~/.ereshkigal.conf')")
(asked_for, args) = parser.parse_args() (asked_for, args) = parser.parse_args()
@ -639,10 +640,10 @@ if __name__ == "__main__":
logging.debug(logmsg) logging.debug(logmsg)
logging.debug("Log to stdout") logging.debug("Log to stdout")
logging.debug("Asked for: %s" % asked_for) logging.debug("Asked for: %s", asked_for)
# unfortunately, asked_for class has no __len__ method in python 2.4.3 (bug?) # unfortunately, asked_for class has no __len__ method in python 2.4.3 (bug?)
#if len(asked_for) > 1: # if len(asked_for) > 1:
# parser.error("asked_for are mutually exclusive") # parser.error("asked_for are mutually exclusive")
config = configparser.ConfigParser() config = configparser.ConfigParser()
@ -650,18 +651,16 @@ if __name__ == "__main__":
try: try:
config.read(asked_for.config_file) config.read(asked_for.config_file)
except configparser.MissingSectionHeaderError: except configparser.MissingSectionHeaderError:
logging.error("'%s' contains no known configuration" % asked_for.config_file) logging.error("'%s' contains no known configuration", asked_for.config_file)
else: else:
try: try:
config.read('~/.ereshkigal.conf') config.read('~/.ereshkigal.conf')
except configparser.MissingSectionHeaderError: except configparser.MissingSectionHeaderError:
logging.error("'%s' contains no known configuration" % asked_for.config_file) logging.error("'%s' contains no known configuration", asked_for.config_file)
# Load autossh instances by sections: [expected] # Load autossh instances by sections: [expected]
# if config['expected']: # if config['expected']:
if asked_for.curses: if asked_for.curses:
logging.debug("Entering curses mode") logging.debug("Entering curses mode")
import curses import curses
@ -687,7 +686,7 @@ if __name__ == "__main__":
scr.keypad(1) scr.keypad(1)
# create the monitor # create the monitor
mc = CursesMonitor( scr ) mc = CursesMonitor(scr)
# call the monitor # call the monitor
mc() mc()
@ -706,13 +705,12 @@ if __name__ == "__main__":
# print the traceback # print the traceback
traceback.print_exc() traceback.print_exc()
elif asked_for.connections: elif asked_for.connections:
logging.debug("Entering connections mode") logging.debug("Entering connections mode")
tp = TunnelsParser() tp = TunnelsParser()
tp.update() tp.update()
# do not call update() but only get connections # do not call update() but only get connections
logging.debug("UID: %i." % os.geteuid()) logging.debug("UID: %i.", os.geteuid())
# if os.geteuid() == 0: # if os.geteuid() == 0:
for t in tp.tunnels: for t in tp.tunnels:
for c in tp.tunnels[t].connections: for c in tp.tunnels[t].connections:
@ -721,7 +719,6 @@ if __name__ == "__main__":
# else: # else:
# logging.error("Only root can see SSH tunnels connections.") # logging.error("Only root can see SSH tunnels connections.")
elif asked_for.tunnels: elif asked_for.tunnels:
logging.debug("Entering tunnel mode") logging.debug("Entering tunnel mode")
tp = TunnelsParser() tp = TunnelsParser()
@ -731,7 +728,6 @@ if __name__ == "__main__":
for t in tp.tunnels: for t in tp.tunnels:
print(tp.tunnels[t].repr_tunnel()) print(tp.tunnels[t].repr_tunnel())
else: else:
logging.debug("Entering default mode") logging.debug("Entering default mode")
tp = TunnelsParser() tp = TunnelsParser()
@ -749,4 +745,3 @@ if __name__ == "__main__":
# #
# http://en.wikipedia.org/wiki/Ereshkigal # http://en.wikipedia.org/wiki/Ereshkigal
# #