#!/usr/bin/env python #%# family=auto #%# capabilities=autoconf """ Munin plugin that monitors the greyfix triplet database. Author: Tom Hendrikx 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 mail' 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']