diff --git a/plugins/other/greyfix b/plugins/other/greyfix new file mode 100755 index 00000000..ee4da296 --- /dev/null +++ b/plugins/other/greyfix @@ -0,0 +1,185 @@ +#!/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 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']