1 #!/usr/bin/perl
3 =head1 NAME
5 Collectd - plugin for filling collectd with stats
7 =head1 INSTALLATION
9 Just copy Collectd.pm into your SpamAssassin Plugin path
10 (e.g /usr/share/perl5/Mail/SpamAssassin/Plugin/) and
11 add a loadplugin call into your init.pre file.
13 =head1 SYNOPSIS
15 loadplugin Mail::SpamAssassin::Plugin::Collectd
17 =head1 USER SETTINGS
19 =over 4
21 =item collectd_socket [ socket path ] (default: /tmp/.collectd-email)
23 Where the collectd socket is
25 =cut
27 =item collectd_buffersize [ size ] (default: 256)
29 the email plugin uses a fixed buffer, if a line exceeds this size
30 it has to be continued in another line. (This is of course handled internally)
31 If you have changed this setting please get it in sync with the SA Plugin
32 config.
34 =cut
35 =head1 DESCRIPTION
37 This modules uses the email plugin of collectd from Sebastian Harl to
38 collect statistical informations in rrd files to create some nice looking
39 graphs with rrdtool. They communicate over a unix socket that the collectd
40 plugin creates. The generated graphs will be placed in /var/lib/collectd/email
42 =head1 AUTHOR
44 Alexander Wirt <formorer@formorer.de>
46 =head1 COPYRIGHT
48 Copyright 2006 Alexander Wirt <formorer@formorer.de>
50 This program is free software; you can redistribute it and/or modify
51 it under the the terms of either:
53 a) the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
55 or
57 b) the GPL (http://www.gnu.org/copyleft/gpl.html)
59 use whatever you like more.
61 =cut
63 package Mail::SpamAssassin::Plugin::Collectd;
65 use Mail::SpamAssassin::Plugin;
66 use Mail::SpamAssassin::Logger;
67 use strict;
68 use bytes;
69 use warnings;
70 use Time::HiRes qw(usleep);
71 use IO::Socket;
73 use vars qw(@ISA);
74 @ISA = qw(Mail::SpamAssassin::Plugin);
76 sub new {
77 my ($class, $mailsa) = @_;
79 # the usual perlobj boilerplate to create a subclass object
80 $class = ref($class) || $class;
81 my $self = $class->SUPER::new($mailsa);
82 bless ($self, $class);
84 # register our config options
85 $self->set_config($mailsa->{conf});
87 # and return the new plugin object
88 return $self;
89 }
91 sub set_config {
92 my ($self, $conf) = @_;
93 my @cmds = ();
95 push (@cmds, {
96 setting => 'collectd_buffersize',
97 default => 256,
98 type =>
99 $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
100 });
102 push (@cmds, {
103 setting => 'collectd_socket',
104 default => '/tmp/.collectd-email',
105 type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
106 });
108 push (@cmds, {
109 setting => 'collectd_timeout',
110 default => 2,
111 type =>
112 $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
113 });
115 push (@cmds, {
116 setting => 'collectd_retries',
117 default => 3,
118 type =>
119 $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
120 });
123 $conf->{parser}->register_commands(\@cmds);
124 }
126 sub check_end {
127 my ($self, $params) = @_;
128 my $message_status = $params->{permsgstatus};
129 #create new connection to our socket
130 eval {
131 local $SIG{ALRM} = sub { die "Sending to collectd timed out.\n" }; # NB: \n required
133 #generate a timeout
134 alarm $self->{main}->{conf}->{collectd_timeout};
136 my $sock;
137 #try at least $self->{main}->{conf}->{collectd_retries} to get a
138 #connection
139 for (my $i = 0; $i < $self->{main}->{conf}->{collectd_retries} ; ++$i) {
140 last if $sock = new IO::Socket::UNIX
141 ($self->{main}->{conf}->{collectd_socket});
142 #sleep a random value between 0 and 50 microsecs to try for a new
143 #thread
144 usleep(int(rand(50)));
145 }
147 die("could not connect to " .
148 $self->{main}->{conf}->{collectd_socket} . ": $! - collectd plugin disabled") unless $sock;
150 $sock->autoflush(1);
152 my $score = $message_status->{score};
153 #get the size of the message
154 my $body = $message_status->{msg}->{pristine_body};
156 my $len = length($body);
158 if ($message_status->{score} >= $self->{main}->{conf}->{required_score} ) {
159 #hey we have spam
160 print $sock "e:spam:$len\n";
161 } else {
162 print $sock "e:ham:$len\n";
163 }
164 print $sock "s:$score\n";
165 my @tmp_array;
166 my @tests = @{$message_status->{test_names_hit}};
168 my $buffersize = $self->{main}->{conf}->{collectd_buffersize};
169 dbg("collectd: buffersize: $buffersize");
171 while (scalar(@tests) > 0) {
172 push (@tmp_array, pop(@tests));
173 if (length(join(',', @tmp_array) . '\n') > $buffersize) {
174 push (@tests, pop(@tmp_array));
175 if (length(join(',', @tmp_array) . '\n') > $buffersize or scalar(@tmp_array) == 0) {
176 dbg("collectd: this shouldn't happen. Do you have tests"
177 ." with names that have more than ~ $buffersize Bytes?");
178 return 1;
179 } else {
180 dbg ( "collectd: c:" . join(',', @tmp_array) . "\n" );
181 print $sock "c:" . join(',', @tmp_array) . "\n";
182 #clean the array
183 @tmp_array = ();
184 }
185 } elsif ( scalar(@tests) == 0 ) {
186 dbg ( "collectd: c:" . join(',', @tmp_array) . '\n' );
187 print $sock "c:" . join(',', @tmp_array) . "\n";
188 }
189 }
190 close($sock);
191 alarm 0;
192 };
193 if ($@) {
194 my $message = $@;
195 chomp($message);
196 info("collectd: $message");
197 return -1;
198 }
199 }
201 1;
203 # vim: syntax=perl sw=4 ts=4 noet shiftround