#!/usr/bin/python3.6

'''
sagator's logwatch script
(c) 2003-2005,2016-2018 Jan ONDREJ (SAL) <ondrejj(at)salstar.sk>
 
  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.
'''

from __future__ import print_function

import sys, re, os, time

def kilomega(x, v='B'):
    if x>=1024*1024:
      return "%1.4f M%s" % (x/1024.0/1024.0, v)
    elif x>=1024:
      return "%1.4f k%s" % (x/1024.0, v)
    else:
      return "%1.4f %s " % (x, v)

def hodminsec(t):
    return "%d:%02d:%02d" % (t/60/60, t/60%60, t%60)

class a:
  connect = 0
  count,bytes = 0, 0
  time = 0
  found = {}
  found_bytes = {}
  clean,clean_bytes = 0, 0
  infected,infected_bytes = 0, 0
  spam,spam_bytes = 0, 0
  errors = 0
  erra = {}
  warnings = 0
  warna = {}

def spamstring(s):
    return (s[:4]=='SPAM') or (s[-5:]=='&SPAM')

try:
  DATE_RANGE = os.environ['LOGWATCH_DATE_RANGE']
except KeyError:
  DATE_RANGE = 'today'

# regular expressions
base = '^ *([0-9]+): '
reg_conn = re.compile(base+
  'Connection from: ').search
reg_stats = re.compile(base+
  'STATS: ([0-9.]+) seconds, ([0-9]+) bytes, status: (.*)$').search
if DATE_RANGE=='yesterday':
  searchdate = time.strftime("%a %b %e ..:..:.. %Y", 
    time.localtime(time.time()-86400))
elif DATE_RANGE=='today':
  searchdate = time.strftime("%a %b %e ..:..:.. %Y")
elif DATE_RANGE=='all':
  searchdate = '... ... .. ..:..:.. ....'
reg_aconn = re.compile(base+'Connection from: ').search
reg_conn = re.compile(base+'Connection from: .* at ('+searchdate+')').search
reg_exit = re.compile(base+'Child exited ... pid,status: .([0-9]*),').search
reg_err = re.compile(base+'([-a-zA-Z_()]*): *(ERROR|WARNING)[: ]*(.*)$').search
reg_policy = re.compile(base+
  'checkpolicy: '+searchdate+' .* action=(.*)$').search

pids = {}
policy_results = {}
while True:
  line = sys.stdin.readline()
  # EOF?
  if not line:
    break
  # add a new PID into DB
  rconn = reg_aconn(line)
  if rconn:
    reg1 = reg_conn(line)
    if reg1:
      a.connect += 1
      pids[reg1.group(1)] = reg1.group(2)
    else:
      # It is a new connection from this pid, but not for date range
      # If in pids, remove it.
      try:
        del pids[rconn.group(1)]
      except KeyError:
        pass
  # remove PID from DB if exited
  reg2 = reg_exit(line)
  if reg2:
    try:
      del pids[reg2.group(2)]
    except:
      pass
  reg = reg_err(line)
  if reg:
    # is this PID in DB?
    if reg.group(1) not in pids:
      continue
    # is it an ERROR or WARNING?
    if reg.group(3)=="ERROR":
      # error
      a.errors += 1
      try:
        a.erra[reg.group(4)] += 1
      except KeyError:
        a.erra[reg.group(4)] = 1
    else:
      # warning
      a.warnings += 1
      try:
        a.warna[reg.group(4)] += 1
      except KeyError:
        a.warna[reg.group(4)] = 1
  reg = reg_stats(line)
  if reg:
    # is this PID in DB?
    if reg.group(1) not in pids:
      continue
    a.count += 1
    a.time += float(reg.group(2))
    bytes = reg.group(3)
    name = reg.group(4)
    a.bytes += int(bytes)
    if (len(name)>80) and (not spamstring(name)):
      # error?
      a.errors += 1
      try:
        a.erra[name] += 1
      except KeyError:
        a.erra[name] = 1
    elif name=='CLEAN':
      a.clean += 1
      a.clean_bytes += int(bytes)
    else:
      if spamstring(name):
        a.spam += 1
        a.spam_bytes += int(bytes)
      else:
        a.infected += 1
        a.infected_bytes += int(bytes)
      try:
        a.found[name] += 1
        a.found_bytes[name] += int(bytes)
      except KeyError:
        a.found[name] = 1
        a.found_bytes[name] = int(bytes)
  # policy processing
  reg = reg_policy(line)
  if reg:
    policy_result = reg.group(2)
    if policy_result.startswith("defer_if_permit Greylisted for"):
      policy_result = "defer_if_permit Greylisted"
    if policy_result in policy_results:
      policy_results[policy_result] += 1
    else:
      policy_results[policy_result] = 1

#print '\n--------------------- SAGATOR -----------------------\n'

if a.count>0:
  print('                      Email count            Bytes')
  print('Total:               %6d              %12s' % \
    (a.count,kilomega(a.bytes)))
  if a.bytes==0: a.bytes = 0.00001
  print('Clean:               %6d [%3d%%]       %12s [%3d%%]' % \
    (a.clean, a.clean*100/a.count,
     kilomega(a.clean_bytes), a.clean_bytes*100/a.bytes))
  print('Viruses:             %6d [%3d%%]       %12s [%3d%%]' % \
    (a.infected, a.infected*100/a.count,
     kilomega(a.infected_bytes), a.infected_bytes*100/a.bytes))
  print('Spams:               %6d [%3d%%]       %12s [%3d%%]' % \
    (a.spam, a.spam*100/a.count,
     kilomega(a.spam_bytes), a.spam_bytes*100/a.bytes))
  print('')
  print('Consumed time:     %8s' % (hodminsec(a.time)))
  print('Connections:         %6d' % (a.connect))

  keys = list(a.found.keys())
  if keys:
    keys.sort(key=lambda x: (a.found[x], a.found_bytes[x], x), reverse=True)
    print('')
    for k in keys:
      print('%6d [%12s]: %s' % (a.found[k],kilomega(a.found_bytes[k]),k))

  if a.errors>0:
    print('\nErrors: %d' % a.errors)
    for k, v in list(a.erra.items()):
      print("%5d: %s" % (v, k))
  if a.warnings>0:
    print('\nWarnings: %d' % a.warnings)
    for k, v in list(a.warna.items()):
      print("%5d: %s" % (v, k))

if policy_results:
  print('\nPolicy filter results:')
  total = sum(policy_results.values())
  for k, v in sorted(list(policy_results.items()), key=lambda x: x[1], reverse=True):
    print("%6d [%3d%%]: %s" % (v, v*100/total, k))

#print '\n--------------------- SAGATOR -----------------------\n'
