# 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