commit 84101b41b0eb76939ae00fd9b18e110604a051e7
Author: Seth Vidal <skvidal(a)fedoraproject.org>
Date: Tue Sep 6 16:08:20 2011 -0400
make the logins module use a sqlite db and some fuzzy matching to determine if the
user
login needs to be reported. This will end up showing only new or 'odd' logins
by your users.
etc/modules.d/logins.conf.in | 11 ++
modules/logins_mod.py | 235 ++++++++++++++++++++++++++++++++++--------
2 files changed, 201 insertions(+), 45 deletions(-)
---
diff --git a/etc/modules.d/logins.conf.in b/etc/modules.d/logins.conf.in
index 17708d6..aab22ed 100644
--- a/etc/modules.d/logins.conf.in
+++ b/etc/modules.d/logins.conf.in
@@ -20,6 +20,7 @@ enable_dovecot = 0
enable_courier = 0
enable_imp = 0
enable_proftpd = 0
+
##
# This is a fun setting. You can list domains that are "safe" here.
# E.g. if your org's domain is
example.com and you generally don't
@@ -37,3 +38,13 @@ safe_domains = .*
# If you have too many systems, wide-scale probing may turn ugly. This
# will collapse the reports.
systems_collapse = 10
+
+# comma/space separated list of users to ignore - unknown is the internal "no user
given"
+ignore_users = unknown
+# path to where we keep the logins db
+loginsdb_path = '/var/lib/epylog/logins_db.sqlite'
+# clean up entries in the db which are more than this many days old
+remove_older_than = 14
+# time fuzz - default time (in minutes) which is valid fuzzy match for a login to not be
listed
+time_fuzz = 60
+
diff --git a/modules/logins_mod.py b/modules/logins_mod.py
index 983e203..7e0b504 100644
--- a/modules/logins_mod.py
+++ b/modules/logins_mod.py
@@ -1,3 +1,5 @@
+
+
#!/usr/bin/python -tt
"""
Description will eventually go here.
@@ -28,10 +30,26 @@ Description will eventually go here.
import sys
import re
-
+import time
+import os
+import sqlite3 as sqlite
sys.path.insert(0, '../py/')
from epylog import Result, InternalModule
+
+def executeSQL(cursor, query, params=None):
+ """
+ Execute a python 2.5 (sqlite3) style query.
+
+ @param cursor: A sqlite cursor
+ @param query: The query to execute
+ @param params: An optional list of parameters to the query
+ """
+ if params is None:
+ return cursor.execute(query)
+
+ return cursor.execute(query, params)
+
class logins_mod(InternalModule):
def __init__(self, opts, logger):
InternalModule.__init__(self)
@@ -46,7 +64,19 @@ class logins_mod(InternalModule):
self.root_failure = 12
self.pam_ignore = []
self.xinetd_ignore = []
-
+ self.logins_db = opts.get('loginsdb_path',
'/var/lib/epylog/logins_db.sqlite') # where to keep the loginsdb
+ self.time_fuzz = int(opts.get('time_fuzz', 60)) # how much to fuzz the
time in minutes (default 60m)
+ remove_older_than = int(opts.get('remove_older_than', 14)) # time in days
to start remove from the db
+ self.oldest_to_keep = time.time() - (remove_older_than*86400)
+ if remove_older_than == '0': # if it is zero then don't delete any,
ever - hah your funeral
+ self.oldest_to_keep = None
+
+ ig_users = opts.get('ignore_users', '')
+ ig_users.replace(',',' ')
+ self.ignore_users = ig_users.split(' ')
+
+ self.db_cx = None
+
##
# PAM reports
#
@@ -250,7 +280,7 @@ class logins_mod(InternalModule):
mo = self.pam_failure_more_re.search(message)
if mo: mult += int(mo.group(1))
restuple = self._mk_restuple(action, system, service, user,
- byuser, rhost)
+ byuser, rhost, linemap['stamp'])
return {restuple: mult}
def pam_open(self, linemap):
@@ -269,7 +299,7 @@ class logins_mod(InternalModule):
return None
user, byuser, byuid = mo.groups()
if byuser == '': byuser = self.getuname(int(byuid))
- restuple = self._mk_restuple(action, system, service, user, byuser, '')
+ restuple = self._mk_restuple(action, system, service, user, byuser, '',
linemap['stamp'])
return {restuple: mult}
def pam_baduser(self, linemap):
@@ -281,7 +311,7 @@ class logins_mod(InternalModule):
return None
user = mo.group(1)
service = self._get_pam_service(message)
- restuple = self._mk_restuple(action, system, service, user, '',
'')
+ restuple = self._mk_restuple(action, system, service, user, '',
'', linemap['stamp'])
return {restuple: mult}
def pam_chelper_failure(self, linemap):
@@ -293,7 +323,7 @@ class logins_mod(InternalModule):
return None
user = mo.group(1)
service = self._get_pam_service(message)
- restuple = self._mk_restuple(action, system, service, user, '',
'')
+ restuple = self._mk_restuple(action, system, service, user, '',
'', linemap['stamp'])
return {restuple: mult}
def pam_krb5_open(self, linemap):
@@ -311,7 +341,7 @@ class logins_mod(InternalModule):
#
result = self.general_ignore(linemap)
return result
- restuple = self._mk_restuple(action, system, service, user, '',
'')
+ restuple = self._mk_restuple(action, system, service, user, '',
'', linemap['stamp'])
return {restuple: mult}
def pam_krb5_failure(self, linemap):
@@ -333,7 +363,7 @@ class logins_mod(InternalModule):
#
result = self.general_ignore(linemap)
return result
- restuple = self._mk_restuple(action, system, service, user, '',
'')
+ restuple = self._mk_restuple(action, system, service, user, '',
'', linemap['stamp'])
return {restuple: mult}
def xinetd_start(self, linemap):
@@ -350,7 +380,7 @@ class logins_mod(InternalModule):
#
result = self.general_ignore(linemap)
return result
- restuple = self._mk_restuple(action, system, service, '', '',
'')
+ restuple = self._mk_restuple(action, system, service, '', '',
'', linemap['stamp'])
return {restuple: mult}
def sshd_open(self, linemap):
@@ -369,7 +399,7 @@ class logins_mod(InternalModule):
if not service: service = 'ssh1'
service = '%s(%s)' % (service, method)
restuple = self._mk_restuple(action, system, service, user,
- ruser, rhost)
+ ruser, rhost, linemap['stamp'])
return {restuple: mult}
def sshd_failure(self, linemap):
@@ -384,7 +414,7 @@ class logins_mod(InternalModule):
rhost = self.gethost(rhost)
if not service: service = 'ssh1'
service = '%s(%s)' % (service, method)
- restuple = self._mk_restuple(action, system, service, user, '', rhost)
+ restuple = self._mk_restuple(action, system, service, user, '', rhost,
linemap['stamp'])
return {restuple: mult}
def uw_imap_failure(self, linemap):
@@ -398,7 +428,7 @@ class logins_mod(InternalModule):
return None
user, rhost = mo.groups()
rhost = self.gethost(rhost)
- restuple = self._mk_restuple(action, system, service, user, '', rhost)
+ restuple = self._mk_restuple(action, system, service, user, '', rhost,
linemap['stamp'])
return {restuple: mult}
def uw_imap_open(self, linemap):
@@ -412,7 +442,7 @@ class logins_mod(InternalModule):
return None
user, rhost = mo.groups()
rhost = self.gethost(rhost)
- restuple = self._mk_restuple(action, system, service, user, '', rhost)
+ restuple = self._mk_restuple(action, system, service, user, '', rhost,
linemap['stamp'])
return {restuple: mult}
def dovecot_open(self, linemap):
@@ -425,7 +455,7 @@ class logins_mod(InternalModule):
return None
user, rhost = mo.groups()
rhost = self.gethost(rhost)
- restuple = self._mk_restuple(action, system, service, user, '', rhost)
+ restuple = self._mk_restuple(action, system, service, user, '', rhost,
linemap['stamp'])
return {restuple: mult}
def dovecot_failure(self, linemap):
@@ -439,7 +469,7 @@ class logins_mod(InternalModule):
rhost = mo.group(1)
rhost = self.gethost(rhost)
user = 'unknown'
- restuple = self._mk_restuple(action, system, service, user, '', rhost)
+ restuple = self._mk_restuple(action, system, service, user, '', rhost,
linemap['stamp'])
return {restuple: mult}
def courier_open(self, linemap):
@@ -452,7 +482,7 @@ class logins_mod(InternalModule):
service, user, rhost = mo.groups()
service = '%s(cr)' % service
rhost = self.gethost(rhost)
- restuple = self._mk_restuple(action, system, service, user, '', rhost)
+ restuple = self._mk_restuple(action, system, service, user, '', rhost,
linemap['stamp'])
return {restuple: mult}
def courier_failure(self, linemap):
@@ -466,7 +496,7 @@ class logins_mod(InternalModule):
service = '%s(cr)' % service
rhost = self.gethost(rhost)
user = 'unknown'
- restuple = self._mk_restuple(action, system, service, user, '', rhost)
+ restuple = self._mk_restuple(action, system, service, user, '', rhost,
linemap['stamp'])
return {restuple: mult}
def proftpd_open(self, linemap):
@@ -479,7 +509,7 @@ class logins_mod(InternalModule):
service = 'ftp(pro)'
rhost, user = mo.groups()
rhost = self.gethost(rhost)
- restuple = self._mk_restuple(action, system, service, user, '', rhost)
+ restuple = self._mk_restuple(action, system, service, user, '', rhost,
linemap['stamp'])
return {restuple: mult}
def proftpd_failure(self, linemap):
@@ -492,7 +522,7 @@ class logins_mod(InternalModule):
service = 'ftp(pro)'
rhost, user = mo.groups()
rhost = self.gethost(rhost)
- restuple = self._mk_restuple(action, system, service, user, '', rhost)
+ restuple = self._mk_restuple(action, system, service, user, '', rhost,
linemap['stamp'])
return {restuple: mult}
def imp2_failure(self, linemap):
@@ -505,7 +535,7 @@ class logins_mod(InternalModule):
rhost, system, user = mo.groups()
rhost = self.gethost(rhost)
service = 'IMP2'
- restuple = self._mk_restuple(action, system, service, user, '', rhost)
+ restuple = self._mk_restuple(action, system, service, user, '', rhost,
linemap['stamp'])
return {restuple: mult}
def imp2_open(self, linemap):
@@ -518,7 +548,7 @@ class logins_mod(InternalModule):
rhost, system, user = mo.groups()
rhost = self.gethost(rhost)
service = 'IMP2'
- restuple = self._mk_restuple(action, system, service, user, '', rhost)
+ restuple = self._mk_restuple(action, system, service, user, '', rhost,
linemap['stamp'])
return {restuple: mult}
def imp3_failure(self, linemap):
@@ -531,7 +561,7 @@ class logins_mod(InternalModule):
rhost, system, user = mo.groups()
rhost = self.gethost(rhost)
service = 'IMP3'
- restuple = self._mk_restuple(action, system, service, user, '', rhost)
+ restuple = self._mk_restuple(action, system, service, user, '', rhost,
linemap['stamp'])
return {restuple: mult}
def imp3_open(self, linemap):
@@ -544,7 +574,7 @@ class logins_mod(InternalModule):
user, rhost, system = mo.groups()
rhost = self.gethost(rhost)
service = 'IMP3'
- restuple = self._mk_restuple(action, system, service, user, '', rhost)
+ restuple = self._mk_restuple(action, system, service, user, '', rhost,
linemap['stamp'])
return {restuple: mult}
def cyrus_failure(self,linemap):
@@ -557,7 +587,7 @@ class logins_mod(InternalModule):
return None
rhost, user = mo.groups()
rhost = self.gethost(rhost)
- restuple = self._mk_restuple(action, system, service, user, '', rhost)
+ restuple = self._mk_restuple(action, system, service, user, '', rhost,
linemap['stamp'])
return {restuple: mult}
def cyrus_open(self,linemap):
@@ -570,7 +600,7 @@ class logins_mod(InternalModule):
return None
rhost, user = mo.groups()
rhost = self.gethost(rhost)
- restuple = self._mk_restuple(action, system, service, user, '', rhost)
+ restuple = self._mk_restuple(action, system, service, user, '', rhost,
linemap['stamp'])
return {restuple: mult}
def qpopper_failure(self, linemap):
@@ -583,9 +613,9 @@ class logins_mod(InternalModule):
user, rhost = mo.groups()
rhost = self.gethost(rhost)
service = 'qpopper'
- restuple = self._mk_restuple(action, system, service, user, '', rhost)
+ restuple = self._mk_restuple(action, system, service, user, '', rhost,
linemap['stamp'])
return {restuple: mult}
-
+
def qpopper_open(self, linemap):
action = self.open
system, message, mult = self.get_smm(linemap)
@@ -596,17 +626,17 @@ class logins_mod(InternalModule):
user, rhost = mo.groups()
rhost = self.gethost(rhost)
service = 'qpopper'
- restuple = self._mk_restuple(action, system, service, user, '', rhost)
+ restuple = self._mk_restuple(action, system, service, user, '', rhost,
linemap['stamp'])
return {restuple: mult}
##
# HELPER METHODS
#
- def _mk_restuple(self, action, system, service, user, byuser, rhost):
+ def _mk_restuple(self, action, system, service, user, byuser, rhost, stamp):
if user == '': user = 'unknown'
if user == 'root' or user == 'ROOT':
action += 10
remote = self._mk_userat(byuser, rhost)
- restuple = (action, system, service, remote)
+ restuple = (action, system, service, remote, stamp)
else:
if rhost:
match = 0
@@ -617,7 +647,7 @@ class logins_mod(InternalModule):
if not match:
tmp = {'system': system, 'rhost': rhost}
system = self.untrusted_host % tmp
- restuple = (action, user, service, system)
+ restuple = (action, user, service, system, stamp)
return restuple
def _mk_dots(self, str, lim):
@@ -651,6 +681,69 @@ class logins_mod(InternalModule):
if mo: service = mo.group(1)
return service
+
+
+ def _check_for_login(self, username, service, hostname, tid):
+ # check if we have a login which matches in the db,
+
+ if not os.path.exists(self.logins_db):
+ return False
+
+ p = (username, service, hostname, self.last_entry)
+ q = "select * from logins where username=? and service=? and host=? and pkey
<= ?"
+ if not self.db_cx:
+ self._db_cx()
+
+ cur = self.db_cx.cursor()
+ ob = executeSQL(cur, q, p)
+ for i in ob:
+ # if we get any matches if they match within the fuzzed time then
+ # don't show it
+ if abs(tid - i[7]) <= self.time_fuzz:
+ return True
+ return False
+
+ def _add_login(self, username, action, hostname, timestamp, service, u_from=None):
+ if not self.db_cx:
+ self._db_cx()
+
+ cur = self.db_cx.cursor()
+ t_st = time.localtime(int(timestamp))
+ time_in_day = int(t_st[3]*60) + int(t_st[4])
+ q = "insert into logins values (NULL, ?, ?, ?, ?, ?, ?, ?)"
+ p = (action, username, hostname, u_from, service, timestamp, time_in_day)
+ cur.execute(q, p)
+
+
+ def _db_cx(self):
+ if not os.path.exists(self.logins_db):
+ self.db_cx = self._setup_logins_db()
+ else:
+ self.db_cx = sqlite.Connection(self.logins_db)
+
+ q = "select max(pkey) from logins"
+ last_e = executeSQL(self.db_cx.cursor(), q)
+ val = last_e.fetchone()[0]
+ if not val:
+ self.last_entry = 0
+ else:
+ self.last_entry = val
+
+ def _setup_logins_db(self):
+ schema = [
+ """PRAGMA synchronous="OFF";""",
+ """CREATE TABLE logins ( pkey INTEGER PRIMARY KEY, action INTEGER,
+ username TEXT, host TEXT, u_from TEXT, service TEXT,
+ stamp INTEGER, time_in_day INTEGER);""",
+ ]
+
+ cx = sqlite.Connection(self.logins_db)
+ cursor = cx.cursor()
+ for cmd in schema:
+ executeSQL(cursor, cmd)
+
+ return cx
+
##
# FINALIZE!!
#
@@ -661,27 +754,71 @@ class logins_mod(InternalModule):
#
report = ''
rep = {}
+
+ # FIXME
+ # go through each item in the rs
+ # feed them into the db
+ # pull back from the db all the info you need for the report
+ # simplifies a lot of this code
+
+ # chuck it all into the db
+ for (rt,count) in rs.items():
+ if rt[0] in (self.root_failure, self.root_open):
+ (action, host, service, remote, stamp) = rt
+ user = 'root'
+ elif rt[0] in (self.open, self.failure):
+ (action, user, service, host, stamp) = rt
+ remote = 'NULL'
+ else:
+ continue
+ if user in self.ignore_users:
+ continue
+ for num in range(0, count):
+ self._add_login(user, action, host, stamp, service, remote)
+ self.db_cx.commit()
+
+ #return "lalallala"
+
for action in [self.root_failure, self.root_open,
- self.failure, self.open]:
+ self.failure, self.open]:
rep[action] = ''
+ per_user = {}
flipper = ''
- for key in rs.get_distinct((action,)):
+ q = """select distinct username, service, host from logins
where action = ? and pkey > ?"""
+ p = (action, self.last_entry)
+ act_tuple = [(i[0],i[1], i[2]) for i in executeSQL(self.db_cx.cursor(), q,
p)]
+
+ for entry in act_tuple:
+ username = entry[0]
+ if username not in per_user:
+ per_user[username] = {}
+ service = entry[1]
+ if service not in per_user[username]:
+ per_user[username][service] = []
+ hn = entry[2]
+ q = """select time_in_day from logins where username = ?
and host = ? and service = ? and action = ? and pkey > ?"""
+ p = (username, hn, service, action, self.last_entry)
+
+ this_logins_times = [row[0] for row in executeSQL(self.db_cx.cursor(),
q, p)]
+ count = 0
+ for t in this_logins_times:
+ if not self._check_for_login(username, service, hn, t):
+ # DEBUG print 'new login %s %s %s %s' % (username,
service, hn, t)
+ count += 1
+
+ if count:
+ per_user[username][service].append('%s(%d)' % (hn, count))
+
+ blank = 0
+ for username in sorted(per_user):
if flipper: flipper = ''
else: flipper = self.flip
- service_rep = []
- for service in rs.get_distinct((action, key)):
- mymap = rs.get_submap((action, key, service))
- key2s = []
- for key2 in mymap.keys():
- field = key2[0]
- key2s.append('%s(%d)' % (field, mymap[key2]))
- service_rep.append([service, ', '.join(key2s)])
- blank = 0
- for svcrep in service_rep:
+ for (svc,reps) in per_user[username].items():
if blank: key = ' '
else: blank = 1
- rep[action] += self.line_rep % (flipper, key,
- svcrep[0], svcrep[1])
+ if reps:
+ rep[action] += self.line_rep % (flipper, username,
+ svc, ', '.join(reps))
if rep[self.root_failure]:
report += self.subreport_wrap % (self.root_failures_title,
@@ -697,6 +834,14 @@ class logins_mod(InternalModule):
rep[self.open])
report = self.report_wrap % report
+
+ if self.oldest_to_keep:
+ print self.oldest_to_keep
+ q = """delete from logins where stamp <
?"""
+ p = (self.oldest_to_keep,)
+ executeSQL(self.db_cx.cursor(), q, p)
+ self.db_cx.commit()
+
return report
if __name__ == '__main__':