186 lines
5.5 KiB
Plaintext
186 lines
5.5 KiB
Plaintext
|
#!/usr/bin/env python
|
||
|
#%# family=auto
|
||
|
#%# capabilities=autoconf
|
||
|
|
||
|
"""
|
||
|
Munin plugin that monitors the greyfix triplet database.
|
||
|
Author: Tom Hendrikx <tom@whyscream.net> 2011-08-05
|
||
|
|
||
|
This plugin monitors the Greyfix greylisting tool for Postfix.
|
||
|
It creates a nice overview of the number of triplets in the greyfix database,
|
||
|
dividing the database population in configurable ages.
|
||
|
|
||
|
For more information about greyfix: http://trac.kim-minh.com/greyfix/
|
||
|
|
||
|
Installation
|
||
|
============
|
||
|
|
||
|
To install, create a symlink to the plugin in the munin plugin directory:
|
||
|
$ ln -s /path/to/plugin/greyfix /etc/munin/plugins/greyfix
|
||
|
|
||
|
Configuration
|
||
|
=============
|
||
|
|
||
|
There are some settings that can be tweaked by adding statements to the
|
||
|
munin-node config:
|
||
|
|
||
|
[greyfix]
|
||
|
# run plugin as the same user as postfix does
|
||
|
user nobody
|
||
|
# path to greyfix binary (default: /usr/sbin/greyfix)
|
||
|
env.greyfix /usr/local/sbin/greyfix
|
||
|
# the length of each graph step in days (default: 7)
|
||
|
env.step_size 3
|
||
|
# the number of steps to graph (default: 11)
|
||
|
env.num_steps 47
|
||
|
# graph the greylisted triplets separate from the whitelisted ones (default: yes)
|
||
|
env.greylist_step no
|
||
|
|
||
|
Please note that the last step has no end date, so it includes all triplets
|
||
|
older than the second last step. I.e., the defaults (as named above) create a
|
||
|
graph that shows 10 steps of one week each, and one last step for everything
|
||
|
older than 10 weeks. Also, the separate greylist step is not considered
|
||
|
when applying num_steps.
|
||
|
|
||
|
"""
|
||
|
|
||
|
# Settings: all of these can be redefined in the munin-node config
|
||
|
settings = {
|
||
|
'greyfix': '/usr/sbin/greyfix',
|
||
|
'step_size': 7,
|
||
|
'num_steps': 11,
|
||
|
'greylist_step': 'yes'
|
||
|
}
|
||
|
|
||
|
|
||
|
import os
|
||
|
import sys
|
||
|
import subprocess
|
||
|
import datetime
|
||
|
|
||
|
|
||
|
def greyfix_parse_triplets():
|
||
|
"""Parse output of greyfix --dump-triplets into something that we can use.
|
||
|
|
||
|
The output of the command has the following columns:
|
||
|
sender_ip, sender_email, recipient_email, timestamp_first_seen, timestamp_last_seen, block_count, pass_count
|
||
|
|
||
|
Also see http://groups.google.com/group/greyfix/browse_thread/thread/510687a9ed94fc2c"""
|
||
|
|
||
|
greyfix = subprocess.Popen(args=[ settings['greyfix'], '--dump-triplets'], stdout=subprocess.PIPE)
|
||
|
stdout = greyfix.communicate()[0]
|
||
|
if greyfix.returncode > 0:
|
||
|
print '# greyfix exited with exit code %i' % (greyfix.returncode)
|
||
|
sys.exit(greyfix.returncode)
|
||
|
|
||
|
triplets = []
|
||
|
for line in stdout.split("\n"):
|
||
|
triplet = line.split("\t")
|
||
|
if len(triplet) == 7:
|
||
|
triplet[3] = datetime.datetime.strptime(triplet[3], '%c')
|
||
|
triplet[4] = datetime.datetime.strptime(triplet[4], '%c')
|
||
|
triplet[5] = int(triplet[5])
|
||
|
triplet[6] = int(triplet[6])
|
||
|
triplets.append(triplet)
|
||
|
|
||
|
return triplets
|
||
|
|
||
|
|
||
|
def convert_step_to_days(step):
|
||
|
"""Compute the days that are contained in a step, according to the configuration"""
|
||
|
|
||
|
start = settings['step_size'] * step
|
||
|
end = (settings['step_size'] * (step + 1)) - 1
|
||
|
if step >= (settings['num_steps'] -1):
|
||
|
return (start, '')
|
||
|
else:
|
||
|
return (start, end)
|
||
|
|
||
|
|
||
|
def print_fetch():
|
||
|
"""Generates and prints the values as retrieved from greyfix."""
|
||
|
|
||
|
triplets = greyfix_parse_triplets()
|
||
|
now = datetime.datetime.now()
|
||
|
steps = [0 for i in range(0, settings['num_steps'])]
|
||
|
greylist_step = 0
|
||
|
|
||
|
for triplet in triplets:
|
||
|
if settings['greylist_step'] == 'yes' and triplet[6] == 0:
|
||
|
greylist_step = greylist_step +1
|
||
|
continue;
|
||
|
|
||
|
delta = now - triplet[3]
|
||
|
step = delta.days / settings['step_size']
|
||
|
step = min(step, settings['num_steps'] -1)
|
||
|
# count the number of triplets in a group
|
||
|
steps[ step ] = steps[ step ] +1
|
||
|
|
||
|
# print result counts for each group
|
||
|
if settings['greylist_step'] == 'yes':
|
||
|
print 'gl.value %i' % greylist_step
|
||
|
for step, count in enumerate(steps):
|
||
|
fieldname = 'd%s_%s' % convert_step_to_days(step)
|
||
|
print '%s.value %i' % (fieldname, count)
|
||
|
|
||
|
|
||
|
def print_config():
|
||
|
"""Generates and prints a munin config for a given chart."""
|
||
|
|
||
|
print 'graph_title Greyfix triplets by age'
|
||
|
print 'graph_vlabel Number of triplets'
|
||
|
print 'graph_info The amount of triplets in the greyfix database with a certain age'
|
||
|
print 'graph_category postfix'
|
||
|
print 'graph_total All triplets'
|
||
|
print 'graph_args --lower-limit 0 --base 1000'
|
||
|
|
||
|
if settings['greylist_step'] == 'yes':
|
||
|
print 'gl.label Greylisted'
|
||
|
print 'gl.info The number of greylisted triplets. These did not have a single pass (yet)'
|
||
|
print 'gl.draw AREASTACK'
|
||
|
print 'gl.colour aaaaaa'
|
||
|
|
||
|
steps = range(0, settings['num_steps'])
|
||
|
for step in steps:
|
||
|
days = convert_step_to_days(step)
|
||
|
|
||
|
fieldname = 'd%s_%s' % days
|
||
|
label = 'Whitelisted for %s - %s days' % (days[0], days[1] if days[1] != '' else '...')
|
||
|
info = 'The number of whitelisted triplets that is between %s and %s days old' % (days[0], days[1] if days[1] != '' else '...')
|
||
|
|
||
|
print '%s.label %s' % (fieldname, label)
|
||
|
print '%s.info %s' % (fieldname, info)
|
||
|
print '%s.draw AREASTACK' % fieldname
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
|
||
|
# read settings from config file / environment
|
||
|
for key in settings.iterkeys():
|
||
|
if key in os.environ:
|
||
|
if os.environ[ key ].isdigit():
|
||
|
settings[ key ] = int(os.environ[ key ])
|
||
|
else:
|
||
|
settings[ key ] = os.environ[ key ]
|
||
|
#print '# setting %s updated to: %s' % (key, settings[ key ])
|
||
|
|
||
|
commands = ['fetch', 'autoconf', 'config']
|
||
|
if len(sys.argv) > 1:
|
||
|
command = sys.argv[1]
|
||
|
else:
|
||
|
command = commands[0]
|
||
|
|
||
|
if command not in commands:
|
||
|
print '# command %s unknown, choose one of: %s' % (command, commands)
|
||
|
sys.exit(1)
|
||
|
|
||
|
if command == 'fetch':
|
||
|
print_fetch()
|
||
|
elif command == 'config':
|
||
|
print_config()
|
||
|
elif command == 'autoconf':
|
||
|
if os.path.exists( settings['greyfix'] ):
|
||
|
print 'yes'
|
||
|
else:
|
||
|
print 'no (binary not found at %s)' % settings['greyfix']
|