From c88b5a20e8db770a2d789a6ca3bee36e66d76904 Mon Sep 17 00:00:00 2001
From: Oleksii Shevchuk
Date: Mon, 20 Mar 2017 18:06:50 +0200
Subject: [PATCH] Add last command
---
pupy/modules/last.py | 99 ++++++++++++++++++++++++++
pupy/packages/all/pupyps.py | 138 +++++++++++++++++++++++++++++++++++-
pupy/pupylib/utils/term.py | 4 ++
3 files changed, 240 insertions(+), 1 deletion(-)
create mode 100644 pupy/modules/last.py
diff --git a/pupy/modules/last.py b/pupy/modules/last.py
new file mode 100644
index 00000000..09f62172
--- /dev/null
+++ b/pupy/modules/last.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+from pupylib.PupyModule import *
+from pupylib.PupyCmd import PupyCmd
+from pupylib.utils.rpyc_utils import obtain
+from pupylib.utils.term import colorize
+from datetime import datetime, timedelta
+
+import logging
+
+__class_name__="LastModule"
+
+@config(cat='admin', compat=['linux'])
+class LastModule(PupyModule):
+ """ list terminal sessions """
+
+ dependencies = [ 'pupyps' ]
+ is_module=False
+
+ def init_argparse(self):
+ self.arg_parser = PupyArgumentParser(prog="last", description=self.__doc__)
+ duration = self.arg_parser.add_mutually_exclusive_group()
+ duration.add_argument('-n', '--lines', type=int, help='Get only (n) last records')
+ duration.add_argument('-d', '--days', type=int, help='Get only records for last (n) days')
+ filtering = self.arg_parser.add_mutually_exclusive_group()
+ filtering.add_argument('-x', '--exclude', nargs='+', help='Hide users/hosts/ips')
+ filtering.add_argument('-i', '--include', nargs='+', help='Show users/hosts/ips')
+
+ def run(self, args):
+ try:
+ data = obtain(self.client.conn.modules.pupyps.wtmp())
+ tablein = []
+
+ now = data['now']
+ output = []
+
+ for record in data['records']:
+ if args.days and ( record['start'] + args.days*24*60*60 < now):
+ break
+
+ if args.exclude and any([x in args.exclude for x in record.itervalues()]):
+ continue
+
+ if args.include and not any([x in args.include for x in record.itervalues()]):
+ continue
+
+ if record['type'] not in ('boot', 'process'):
+ continue
+
+ color = ''
+ if record['end'] == -1:
+ if record['user'] == 'root':
+ color = 'lightred'
+ elif record['duration'] < 24*60*60:
+ color = 'lightgreen'
+ elif record['duration'] > 7*24*60*60:
+ color = 'cyan'
+ elif record['user'] == 'root':
+ color = 'yellow'
+ elif record['ip'] != '0.0.0.0':
+ color = 'cyan'
+ elif record['end'] > 24*60*60:
+ color = 'grey'
+ elif record['end'] > 7*24*60*60:
+ color = 'darkgrey'
+
+ if record['type'] == 'boot':
+ color = 'yellow'
+
+ record['start'] = datetime.fromtimestamp(record['start'])
+ record['end'] = datetime.fromtimestamp(
+ record['end']
+ ) if record['end'] != -1 else 'logged in'
+ record['duration'] = timedelta(seconds=int(record['duration']))
+ record['ip'] = '' if record['ip'] == '0.0.0.0' else record['ip']
+
+ if record['type'] == 'boot' and record['end'] == 'logged in':
+ record['end'] = 'up'
+
+ for f in record:
+ record[f] = colorize('{}'.format(record[f]), color)
+
+ output.append(record)
+
+ if args.lines and len(output) >= args.lines:
+ break
+
+
+ columns = [
+ x for x in [
+ 'user', 'line', 'pid' ,'host', 'ip', 'start', 'end', 'duration'
+ ] if any([ bool(y[x]) for y in output ])
+ ]
+
+ self.stdout.write(
+ PupyCmd.table_format(output, wl=columns)
+ )
+
+ except Exception, e:
+ logging.exception(e)
diff --git a/pupy/packages/all/pupyps.py b/pupy/packages/all/pupyps.py
index a37b3854..8f2b282e 100644
--- a/pupy/packages/all/pupyps.py
+++ b/pupy/packages/all/pupyps.py
@@ -7,6 +7,10 @@ import sys
import os
import time
import socket
+import pwd
+import struct
+import netaddr
+import time
families = {
v:k[3:] for k,v in socket.__dict__.iteritems() if k.startswith('AF_')
@@ -187,5 +191,137 @@ def interfaces():
}
}
+def cstring(string):
+ return string[:string.find('\x00')]
+
+def convrecord(item):
+ return item if type(item) in (int,long) else cstring(item)
+
+def wtmp(input='/var/log/wtmp'):
+ retval = []
+ WTmp = struct.Struct('hi32s4s32s256shhiii4I20s')
+
+ login_type = {
+ 0: None,
+ 1: 'runlevel',
+ 2: 'boot',
+ 3: 'time_new',
+ 4: 'time_old',
+ 5: 'init',
+ 6: 'session',
+ 7: 'process',
+ 8: 'terminated',
+ 9: 'accounting',
+ }
+
+ now = time.time()
+
+ with open('/var/log/wtmp') as wtmp:
+ while True:
+ data = wtmp.read(WTmp.size)
+ if not data or len(data) != WTmp.size:
+ break
+
+ items = [ convrecord(x) for x in WTmp.unpack(data) ]
+ itype = login_type[items[0]]
+ if not itype:
+ continue
+
+ if itype in ('runlevel', 'terminated'):
+ for record in retval:
+ if record['end'] == -1:
+ if itype == 'runlevel' and items[4] == 'shutdown':
+ record['end'] = items[9]
+ record['duration'] = record['end'] - record['start']
+ elif itype == 'terminated':
+ if items[1] == 0:
+ if record['line'] == items[2]:
+ record['end'] = items[9]
+ record['duration'] = record['end'] - record['start']
+ break
+ else:
+ if record['type'] in ('session', 'process') and record['pid'] == items[1]:
+ record['end'] = items[9]
+ record['duration'] = record['end'] - record['start']
+ record['termination'] = items[6]
+ record['exit'] = items[7]
+ break
+
+ if record['type'] == 'runlevel' and record['user'] == 'shutdown':
+ break
+
+ ipbin = items[11:15]
+ if all([x==0 for x in ipbin[1:]]):
+ ipaddr = str(netaddr.IPAddress(socket.htonl(ipbin[0])))
+ else:
+ data = struct.pack('IIII', *ipbin).encode('hex')
+ ipaddr = ''
+ while data is not '':
+ ipaddr = ipaddr + ':'
+ ipaddr = ipaddr + data[:4]
+ data = data[4:]
+ ipaddr = str(netaddr.IPAddress(ipaddr[1:]))
+
+ retval.insert(0, {
+ 'type': itype,
+ 'pid': items[1],
+ 'line': items[2],
+ 'id': items[3],
+ 'user': items[4],
+ 'host': items[5],
+ 'termination': items[6],
+ 'exit': items[7],
+ 'session': items[8],
+ 'start': items[9],
+ 'ip': ipaddr,
+ 'end': -1,
+ 'duration': now - items[9]
+ })
+
+ return {
+ 'now': now,
+ 'records': retval
+ }
+
+def lastlog():
+ result = {}
+ LastLog = struct.Struct('I32s256s')
+
+ with open('/var/log/lastlog') as lastlog:
+ uid = 0
+ while True:
+ data = lastlog.read(LastLog.size)
+ if not data or len(data) != LastLog.size:
+ break
+
+ time, line, host = LastLog.unpack(data)
+ line = cstring(line)
+ host = cstring(host)
+ if time:
+ try:
+ name = pwd.getpwuid(uid).pw_name
+ except:
+ name = uid
+
+ result[name] = {
+ 'time': time,
+ 'line': line,
+ 'host': host,
+ }
+ uid += 1
+
+ return result
+
if __name__ == '__main__':
- print psinfo([os.getpid()])[os.getpid()].keys()
+ import datetime
+ for result in wtmp():
+ if result['type'] in ('process', 'boot'):
+ print '{:12s} {:5d} {:7} {:8s} {:8s} {:16s} {:3} {:3} {} - {}'.format(
+ result['type'],
+ result['pid'],
+ result['id'],
+ result['user'], result['line'], result['host'],
+ result['termination'], result['exit'],
+ datetime.datetime.fromtimestamp(result['start']),
+ datetime.datetime.fromtimestamp(result['end']) if result['end'] != -1 else 'logged in',
+ )
diff --git a/pupy/pupylib/utils/term.py b/pupy/pupylib/utils/term.py
index 21c47908..9073b54e 100644
--- a/pupy/pupylib/utils/term.py
+++ b/pupy/pupylib/utils/term.py
@@ -81,8 +81,12 @@ def colorize(s, color):
res="\033[34m"+s+COLOR_STOP
elif color.lower()=="red":
res="\033[31m"+s+COLOR_STOP
+ elif color.lower()=="lightred":
+ res="\033[31;1m"+s+COLOR_STOP
elif color.lower()=="green":
res="\033[32m"+s+COLOR_STOP
+ elif color.lower()=="lightgreen":
+ res="\033[32;1m"+s+COLOR_STOP
elif color.lower()=="yellow":
res="\033[33m"+s+COLOR_STOP
elif color.lower()=="magenta":