#!/usr/bin/python -tt
"""
Description will eventually go here.
"""
##
# Copyright (C) 2003 by Duke University
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA.
#
# $Id: mail_mod.py,v 1.15.2.2 2004/11/15 14:00:33 icon Exp $
#
# @Author Konstantin Ryabitsev <icon@linux.duke.edu>
# @version $Date: 2004/11/15 14:00:33 $
#
import sys
import re
##
# This is for testing purposes, so you can invoke this from the
# modules directory. See also the testing notes at the end of the
# file.
#
sys.path.insert(0, '../py/')
from epylog import Result, InternalModule
class mail_mod(InternalModule):
def __init__(self, opts, logger):
InternalModule.__init__(self)
self.logger = logger
rc = re.compile
postfix_map = {
rc('postfix/smtpd\[\d+\]:\s\S*:'): self.postfix_smtpd,
rc('postfix/n*qmgr\[\d+\]:\s\S*:'): self.postfix_qmgr,
rc('postfix/local\[\d+\]:\s\S*:'): self.postfix_local,
rc('postfix/smtp\[\d+\]:\s\S*:\sto='): self.postfix_smtp
}
sendmail_map = {
rc('sendmail\['): self.sendmail
}
qmail_map = {
rc('qmail:\s\d+.\d+\sinfo\smsg'): self.qmail_infomsg,
rc('qmail:\s\d+.\d+\sstarting\sdelivery'): self.qmail_startdev,
rc('qmail:\s\d+.\d+\sdelivery'): self.qmail_delivery,
rc('qmail:\s\d+.\d+\sbounce\smsg\s\d+'): self.qmail_bounce
}
do_postfix = int(opts.get('enable_postfix', '0'))
do_sendmail = int(opts.get('enable_sendmail', '1'))
do_qmail = int(opts.get('enable_qmail', '0'))
self.regex_map = {}
if do_postfix: self.regex_map.update(postfix_map)
if do_sendmail: self.regex_map.update(sendmail_map)
if do_qmail: self.regex_map.update(qmail_map)
self.toplim = int(opts.get('top_report_limit', '5'))
self.postfix_ident_re = rc('\[\d+\]:\s*([A-Z0-9]*):')
self.postfix_smtpd_re = rc('client=\S*\[(\S*)\]')
self.postfix_qmgr_re = rc('from=(\S*),.*size=(\d*)')
self.postfix_local_re = rc('to=(\S*),.*status=(\S*)\s\((.*)\)')
self.postfix_smtp_re = rc('to=(\S*),.*status=(\S*)')
self.sendmail_ident_re = rc('sendmail\[\d+\]:\s(.*?):')
self.sendmail_fromline_re = rc('from=(.*?),.*size=(\d+),.*relay=(.*)')
self.sendmail_ctladdr_re = rc('to=(\"\|.*?),\sctladdr=(\S+).*stat=(\w+)')
self.sendmail_toline_re = rc('to=(.*?),.*stat=(\w+)')
self.sendmail_from_re = rc('(<.*?>)')
self.sendmail_relay_re = rc('(.*?)\s\[(\S*)\]')
self.qmail_ident_re = rc('qmail:\s(\d+)')
self.qmail_delid_re = rc('delivery\s(\d+):')
self.qmail_infoline_re = rc('bytes\s(\d+)\sfrom\s(<.*?>)')
self.qmail_startdev_re = rc('to\s\S+\s(\S+)')
self.qmail_delivery_re = rc('delivery\s\d+:\s(\S+):')
self.procmail_re = rc('/procmail')
self.bounce = 0
self.success = 1
self.warning = 2
self.procmail = 3
self.delidref = 4
self.delidid = 5
self.report_wrap = '<table border="0" width="100%%" rules="cols" cellpadding="2">%s</table>'
self.subreport_wrap = '<tr><th colspan="2" align="left"><h3><font color="blue">%s</font></h3></th></tr>\n'
self.report_line = '<tr><td valign="top" align="right">%s</td><td valign="top" width="90%%">%s</td></tr>\n'
##
# Line-matching routines
#
def postfix_smtpd(self, linemap):
sys, msg, mult = self.get_smm(linemap)
id = self._get_postfix_id(msg)
self.logger.put(5, 'id=%s' % id)
try:
client = self.postfix_smtpd_re.search(msg).group(1)
client = self.gethost(client)
except: client = None
self.logger.put(5, 'client=%s' % client)
restuple = self._mk_restuple(sys, id, client=client)
return {restuple: mult}
def postfix_qmgr(self, linemap):
sys, msg, mult = self.get_smm(linemap)
id = self._get_postfix_id(msg)
self.logger.put(5, 'id=%s' % id)
try: sender, size = self.postfix_qmgr_re.search(msg).groups()
except: sender, size = (None, 0)
size = int(size)
self.logger.put(5, 'sender=%s, size=%d' % (sender, size))
restuple = self._mk_restuple(sys, id, sender=sender, size=size)
return {restuple: mult}
def postfix_local(self, linemap):
sys, msg, mult = self.get_smm(linemap)
id = self._get_postfix_id(msg)
self.logger.put(5, 'id=%s' % id)
try: to, status, comment = self.postfix_local_re.search(msg).groups()
except:
self.logger.put(5, 'Odd postfix/local line: %s' % msg)
return None
self.logger.put(5, 'to=%s, status=%s, comment=%s' %
(to, status, comment))
if status == 'sent': status = self.success
elif status == 'bounced': status = self.bounce
else: status = self.warning
if self.procmail_re.search(comment): extra = (self.procmail, 1)
else: extra = None
restuple = self._mk_restuple(sys, id, to=to, status=status,
extra=extra)
return {restuple: mult}
def postfix_smtp(self, linemap):
sys, msg, mult = self.get_smm(linemap)
id = self._get_postfix_id(msg)
self.logger.put(5, 'id=%s' % id)
try: to, status = self.postfix_smtp_re.search(msg).groups()
except:
self.logger.put(5, 'Odd postfix/smtp line: %s' % msg)
return None
self.logger.put(5, 'to=%s, status=%s' % (to, status))
if status == 'sent': status = self.success
elif status == 'bounced': status = self.bounce
else: status = self.warning
restuple = self._mk_restuple(sys, id, to=to, status=status)
return {restuple: mult}
def sendmail(self, linemap):
sys, msg, mult = self.get_smm(linemap)
id = self._get_sendmail_id(msg)
mo = self.sendmail_fromline_re.search(msg)
if mo:
sender, size, client = mo.groups()
sender = self._fix_sendmail_address(sender)
size = int(size)
client = self._fix_sendmail_relay(client)
restuple = self._mk_restuple(sys, id, client=client, sender=sender,
size=size)
return {restuple: mult}
mo = self.sendmail_ctladdr_re.search(msg)
if mo:
command, to, status = mo.groups()
extra = None
if self.procmail_re.search(command): extra = (self.procmail, 1)
to = self._fix_sendmail_address(to)
if status == 'Sent': status = self.success
elif status == 'Deferred': status = self.warning
restuple = self._mk_restuple(sys, id, to=to, status=status,
extra=extra)
return {restuple: mult}
mo = self.sendmail_toline_re.search(msg)
if mo:
to, status = mo.groups()
to = self._fix_sendmail_address(to)
if status == 'Sent': status = self.success
elif status == 'Deferred': status = self.warning
restuple = self._mk_restuple(sys, id, to=to, status=status)
return {restuple: mult}
return None
def qmail_infomsg(self, linemap):
sys, msg, mult = self.get_smm(linemap)
id = self._get_qmail_id(msg)
try: size, sender = self.qmail_infoline_re.search(msg).groups()
except:
size = 0
sender = 'unknown'
restuple = self._mk_restuple(sys, id, sender=sender, size=int(size))
return {restuple: mult}
def qmail_startdev(self, linemap):
sys, msg, mult = self.get_smm(linemap)
id = self._get_qmail_id(msg)
delid = self._get_qmail_delid(msg)
try: to = self.qmail_startdev_re.search(msg).group(1)
except: to = 'unknown'
extra = (self.delidref, delid)
restuple = self._mk_restuple(sys, id, to=to, extra=extra)
return {restuple: mult}
def qmail_delivery(self, linemap):
sys, msg, mult = self.get_smm(linemap)
delid = self._get_qmail_delid(msg)
try:
status = self.qmail_delivery_re.search(msg).group(1)
if status == 'success': status = self.success
else: status = self.warning
except: status = self.warning
extra = (self.delidid, 1)
restuple = self._mk_restuple(sys, delid, status=status, extra=extra)
return {restuple: mult}
def qmail_bounce(self, linemap):
sys, msg, mult = self.get_smm(linemap)
id = self._get_qmail_id(msg)
restuple = self._mk_restuple(sys, id, status=self.bounce)
return {restuple: mult}
##
# HElpers
#
def _mk_restuple(self, sys, id, client=None, sender=None, to=None,
size=0, status=None, extra=None):
return (sys, id, client, sender, to, size, status, extra)
def _get_postfix_id(self, str):
try: id = self.postfix_ident_re.search(str).group(1)
except: id = 'unknown'
return id
def _get_sendmail_id(self, str):
try: id = self.sendmail_ident_re.search(str).group(1)
except: id = 'unknown'
return id
def _get_qmail_id(self, str):
try: id = self.qmail_ident_re.search(str).group(1)
except: id = 'unknown'
return id
def _get_qmail_delid(self, str):
try: id = self.qmail_delid_re.search(str).group(1)
except: id = 'unknown'
return id
def _fix_address(self, address):
if address == '<>': address = '<mailer-daemon>'
address = self.htmlsafe(address)
return address
def _fix_sendmail_relay(self, str):
try:
host, ip = self.sendmail_relay_re.search(str).groups()
str = self.gethost(ip)
except: pass
return str
def _fix_sendmail_address(self, str):
try: str = self.sendmail_from_re.search(str).group(1)
except: str = '<%s>' % str
return str
def _get_top_report(self, rs, descr):
toprep = self.subreport_wrap % (descr % self.toplim)
toplist = rs.get_top(self.toplim)
for count, member in toplist:
key = self._fix_address(member[0])
toprep += self.report_line % (str(count), key)
return toprep
def finalize(self, rs):
##
# Go through the results and make sense out of them
#
msgdict = {}
##
# The problem with qmail is that it logs things inconsistently.
# Well, at least not consistently with how epylog expects things
# to be. These are hacks to make it work with qmail.
#
delids = {}
delivs = {}
while 1:
try: msgtup, mult = rs.popitem()
except: break
extra = None
system, id, client, sender, rcpt, size, status, extralst = msgtup
if system is None or (id is None or id is 'unknown'): continue
##
# Accommodate qmail hacks (except procmail, that's for everyone)
#
if extralst is not None:
if extralst[0] == self.procmail: extra = self.procmail
elif extralst[0] == self.delidref:
delids[extralst[1]] = (system, id)
elif extralst[0] == self.delidid:
delivs[id] = status
continue
key = (system, id)
try: msglist = msgdict[key]
except KeyError: msglist = [[], [], [], [], [], []]
if client is not None: msglist[0].append(client)
if sender is not None: msglist[1].append(sender)
if rcpt is not None: msglist[2].append(rcpt)
if size is not None: msglist[3].append(size)
if status is not None: msglist[4].append(status)
if extra is not None: msglist[5].append(extra)
msgdict[key] = msglist
##
# More qmail hacks.
#
if delids:
while 1:
try: delid, key = delids.popitem()
except: break
if key in msgdict:
if delid in delivs: msgdict[key][4].append(delivs[delid])
##
# Do some real calculations now that we have the results collapsed.
#
yrs = Result() # Systems
crs = Result() # Clients (Connecting Relays)
srs = Result() # Senders
rrs = Result() # Recipients
totalmsgs = 0
totalsize = 0
warnings = 0
successes = 0
bounces = 0
procmailed = 0
while 1:
try: key, val = msgdict.popitem()
except: break
system, id = key
yrs.add_result({(system,): 1})
totalmsgs += 1
clients, senders, rcpts, sizes, stati, extras = val
for client in clients: crs.add_result({(client,): 1})
for sender in senders: srs.add_result({(sender,): 1})
for rcpt in rcpts: rrs.add_result({(rcpt,): 1})
for size in sizes: totalsize += size
for status in stati:
if status == self.warning: warnings += 1
elif status == self.success: successes += 1
elif status == self.bounce: bounces += 1
for extra in extras:
if extra == self.procmail: procmailed += 1
rep = self.subreport_wrap % 'General Mail Report'
rep += self.report_line % (totalmsgs, 'Total Messages Processed')
rep += self.report_line % (successes, 'Total Successful Deliveries')
rep += self.report_line % (warnings, 'Total Warnings Issued')
rep += self.report_line % (bounces, 'Total Bounced Messages')
if procmailed:
rep += self.report_line % (procmailed, 'Processed by Procmail')
size, unit = self.mk_size_unit(totalsize)
rep += self.report_line % ('%d %s' % (size, unit),
'Total Transferred Size')
if yrs: rep += self._get_top_report(yrs, 'Top %d active systems')
if crs: rep += self._get_top_report(crs, 'Top %d connecting hosts')
if srs: rep += self._get_top_report(srs, 'Top %d senders')
if rrs: rep += self._get_top_report(rrs, 'Top %d recipients')
report = self.report_wrap % rep
return report
if __name__ == '__main__':
from epylog.helpers import ModuleTest
ModuleTest(mail_mod, sys.argv)
syntax highlighted by Code2HTML, v. 0.9.1