139 lines
3.6 KiB
Python
139 lines
3.6 KiB
Python
# Analyze milter log to find abusers
|
|
import traceback
|
|
import sys
|
|
|
|
def parse_addr(a):
|
|
beg = a.find('<')
|
|
end = a.find('>')
|
|
if beg >= 0:
|
|
if end > beg: return a[beg+1:end]
|
|
return a
|
|
|
|
class Connection(object):
|
|
def __init__(self,dt,tm,id,ip=None,conn=None):
|
|
self.dt = dt
|
|
self.tm = tm
|
|
self.id = id
|
|
if ip:
|
|
_,self.host,self.ip = ip.split(None,2)
|
|
elif conn:
|
|
self.ip = conn.ip
|
|
self.host = conn.host
|
|
self.helo = conn.helo
|
|
self.subject = None
|
|
self.rcpt = []
|
|
self.mfrom = None
|
|
self.helo = None
|
|
self.innoc = []
|
|
self.whitelist = False
|
|
|
|
def connections(fp):
|
|
conndict = {}
|
|
termdict = {}
|
|
for line in fp:
|
|
if line.startswith('{'): continue
|
|
a = line.split(None,4)
|
|
if len(a) < 4: continue
|
|
dt,tm,id,op = a[:4]
|
|
if (id,op) == ('bms','milter'):
|
|
# FIXME: optionally yield all partial connections in conndict
|
|
conndict = {}
|
|
termdict = {}
|
|
continue
|
|
if id[0] == '[' and id[-1] == ']':
|
|
try:
|
|
key = int(id[1:-1])
|
|
except:
|
|
print >>sys.stderr,'bad id:',line.rstrip()
|
|
continue
|
|
else: continue
|
|
if op == 'connect':
|
|
ip = a[4].rstrip()
|
|
conn = Connection(dt,tm,id,ip=ip)
|
|
conndict[key] = conn
|
|
elif op in (
|
|
'DISCARD:','TAG:','CBV:','Large','No',
|
|
'NOTE:','From:','Sender:','TRAIN:'):
|
|
continue
|
|
else:
|
|
op = op.lower()
|
|
try:
|
|
conn = conndict[key]
|
|
except KeyError:
|
|
try:
|
|
conn = termdict[key]
|
|
del termdict[key]
|
|
conndict[key] = conn
|
|
except KeyError:
|
|
print >>sys.stderr,'key error:',line.rstrip()
|
|
continue
|
|
try:
|
|
if op == 'subject:':
|
|
if len(a) > 4:
|
|
conn.subject = a[4].rstrip()
|
|
elif op == 'innoc:':
|
|
conn.innoc.append(a[4].rstrip())
|
|
elif op == 'whitelist':
|
|
conn.whitelist = True
|
|
elif op == 'x-mailer:':
|
|
if len(a) > 4:
|
|
conn.mailer = a[4].rstrip()
|
|
elif op == 'x-guessed-spf:':
|
|
conn.spfguess = a[4]
|
|
elif op == 'received-spf:':
|
|
conn.spfres,conn.spfmsg = a[4].rstrip().split(None,1)
|
|
elif op == 'received:':
|
|
conn.received = a[4].rstrip()
|
|
elif op == 'temp':
|
|
_,conn.tempfile = a[4].rstrip().split(None,1)
|
|
elif op == 'srs':
|
|
_,conn.srsrcpt = a[4].rstrip().split(None,1)
|
|
elif op == 'mail':
|
|
_,conn.mfrom = a[4].rstrip().split(None,1)
|
|
elif op == 'rcpt':
|
|
_,rcpt = a[4].rstrip().split(None,1)
|
|
conn.rcpt.append(rcpt)
|
|
elif op == 'hello':
|
|
_,conn.helo = a[4].rstrip().split(None,1)
|
|
elif op in ('eom','dspam','abort'):
|
|
del conndict[key]
|
|
conn.enddt = dt
|
|
conn.endtm = tm
|
|
conn.result = op
|
|
yield conn
|
|
termdict[key] = Connection(conn.dt,conn.tm,conn.id,conn=conn)
|
|
elif op in ('reject:','dspam:','tempfail:','reject','fail:','honeypot:'):
|
|
del conndict[key]
|
|
conn.enddt = dt
|
|
conn.endtm = tm
|
|
conn.result = op
|
|
conn.resmsg = a[4].rstrip()
|
|
yield conn
|
|
termdict[key] = Connection(conn.dt,conn.tm,conn.id,conn=conn)
|
|
elif op in ('fp:','spam:'):
|
|
del conndict[key]
|
|
termdict[key] = Connection(conn.dt,conn.tm,conn.id,conn=conn)
|
|
else:
|
|
print >>sys.stderr,'unknown op:',line.rstrip()
|
|
except Exception:
|
|
print >>sys.stderr,'error:',line.rstrip()
|
|
traceback.print_exc()
|
|
|
|
if __name__ == '__main__':
|
|
import gzip
|
|
for fn in sys.argv[1:]:
|
|
if fn.endswith('.gz'):
|
|
fp = gzip.open(fn)
|
|
else:
|
|
fp = open(fn)
|
|
for conn in connections(fp):
|
|
if conn.rcpt and conn.mfrom:
|
|
for r in conn.rcpt:
|
|
if r.lower().find('iancarter') > 0: break
|
|
else:
|
|
if conn.mfrom.lower().find('iancarter') < 0: continue
|
|
print >>sys.stderr,conn.result,conn.dt,conn.tm,conn.id,conn.subject,parse_addr(conn.mfrom),
|
|
for a in conn.rcpt:
|
|
print parse_addr(a),
|
|
print
|