Merge remote-tracking branch 'github/pr/2040'
[collectd.git] / contrib / SpamAssassin / Collectd.pm
1 #!/usr/bin/perl
2
3 =head1 NAME
4
5 Collectd - plugin for filling collectd with stats 
6
7 =head1 INSTALLATION
8
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. 
12
13 =head1 SYNOPSIS
14
15   loadplugin    Mail::SpamAssassin::Plugin::Collectd
16
17 =head1 USER SETTINGS
18
19 =over 4
20
21 =item collectd_socket [ socket path ]       (default: /var/run/collectd-email)
22
23 Where the collectd socket is
24
25 =cut 
26
27 =item collectd_buffersize [ size ] (default: 256) 
28
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. 
33
34 =cut 
35
36 =item collectd_timeout [ sec ] (default: 2) 
37
38 if sending data to to collectd takes too long the connection will be aborted. 
39
40 =cut
41
42 =item collectd_retries [ tries ] (default: 3)
43
44 the collectd plugin uses a tread pool, if this is empty the connection fails,
45 the SA Plugin then tries to reconnect. With this variable you can indicate how
46 often it should try. 
47
48 =cut
49
50 =head1 DESCRIPTION
51
52 This modules uses the email plugin of collectd from Sebastian Harl to
53 collect statistical informations in rrd files to create some nice looking
54 graphs with rrdtool. They communicate over a unix socket that the collectd
55 plugin creates. The generated graphs will be placed in /var/lib/collectd/email
56
57 =head1 AUTHOR
58
59 Alexander Wirt <formorer@formorer.de>
60
61 =head1 COPYRIGHT
62
63  Copyright 2006 Alexander Wirt <formorer@formorer.de> 
64  
65  This program is free software; you can redistribute it and/or modify 
66  it under the the terms of either: 
67
68  a) the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
69
70  or
71
72  b) the GPL (http://www.gnu.org/copyleft/gpl.html)  
73
74  use whatever you like more. 
75
76 =cut
77
78 package Mail::SpamAssassin::Plugin::Collectd;
79
80 use Mail::SpamAssassin::Plugin;
81 use Mail::SpamAssassin::Logger;
82 use strict;
83 use bytes; 
84 use warnings;
85 use Time::HiRes qw(usleep);
86 use IO::Socket;
87
88 use vars qw(@ISA);
89 @ISA = qw(Mail::SpamAssassin::Plugin);
90
91 sub new {
92     my ($class, $mailsa) = @_;
93
94     # the usual perlobj boilerplate to create a subclass object
95     $class = ref($class) || $class;
96     my $self = $class->SUPER::new($mailsa);
97     bless ($self, $class);
98
99     # register our config options
100     $self->set_config($mailsa->{conf});
101
102     # and return the new plugin object
103     return $self;
104 }
105
106 sub set_config {
107     my ($self, $conf) = @_;
108     my @cmds = ();
109
110     push (@cmds, {
111             setting => 'collectd_buffersize',
112             default => 256,
113             type =>
114             $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
115         });
116
117     push (@cmds, {
118             setting => 'collectd_socket', 
119             default => '/var/run/collectd-email',
120             type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
121     });
122
123         push (@cmds, {
124                         setting => 'collectd_timeout',
125                         default => 2,
126                         type =>
127                         $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
128         });
129
130         push (@cmds, {
131                         setting => 'collectd_retries',
132                         default => 3,
133                         type =>
134                         $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
135         });
136
137
138     $conf->{parser}->register_commands(\@cmds);
139 }
140
141 sub check_end {
142     my ($self, $params) = @_;
143     my $message_status = $params->{permsgstatus};
144         #create  new connection to our socket
145         eval {
146                 local $SIG{ALRM} = sub { die "Sending to collectd timed out.\n" }; # NB: \n required
147
148                 #generate a timeout
149                 alarm $self->{main}->{conf}->{collectd_timeout};
150
151                 my $sock;
152                 #try at least $self->{main}->{conf}->{collectd_retries} to get a
153                 #connection
154                 for (my $i = 0; $i < $self->{main}->{conf}->{collectd_retries} ; ++$i) {
155                         my ($socket_path) = $self->{main}->{conf}->{collectd_socket} =~ /(.*)/; # Untaint path, which can contain any characters.
156                         last if $sock = new IO::Socket::UNIX $socket_path;
157                         #sleep a random value between 0 and 50 microsecs to try for a new
158                         #thread
159                         usleep(int(rand(50))); 
160                 }
161
162                 die("could not connect to " .
163                                 $self->{main}->{conf}->{collectd_socket} . ": $! - collectd plugin disabled") unless $sock; 
164
165                 $sock->autoflush(1);
166
167                 my $score = $message_status->{score};
168                 #get the size of the message 
169                 my $body = $message_status->{msg}->{pristine_body};
170
171                 my $len = length($body);
172
173                 if ($message_status->{score} >= $self->{main}->{conf}->{required_score} ) {
174                         #hey we have spam
175                         print $sock "e:spam:$len\n";
176                 } else {
177                         print $sock "e:ham:$len\n";
178                 }
179                 print $sock "s:$score\n";
180                 my @tmp_array; 
181                 my @tests = @{$message_status->{test_names_hit}};
182
183                 my $buffersize = $self->{main}->{conf}->{collectd_buffersize}; 
184                 dbg("collectd: buffersize: $buffersize"); 
185
186                 while  (scalar(@tests) > 0) {
187                 push (@tmp_array, pop(@tests)); 
188                         if (length(join(',', @tmp_array) . '\n') > $buffersize) {
189                                 push (@tests, pop(@tmp_array)); 
190                                         if (length(join(',', @tmp_array) . '\n') > $buffersize or scalar(@tmp_array) == 0) {
191                                                 dbg("collectd: this shouldn't happen. Do you have tests"
192                                                         ." with names that have more than ~ $buffersize Bytes?");
193                                                 return 1; 
194                                         } else {
195                                                 dbg ( "collectd: c:" . join(',', @tmp_array) . "\n" ); 
196                                                 print $sock "c:" . join(',', @tmp_array) . "\n"; 
197                                                 #clean the array
198                                                 @tmp_array = ();
199                                         } 
200                         } elsif ( scalar(@tests) == 0 ) {
201                                 dbg ( "collectd: c:" . join(',', @tmp_array) . '\n' );
202                                 print $sock "c:" . join(',', @tmp_array) . "\n";
203                         }
204                 }
205                 close($sock); 
206                 alarm 0; 
207         };
208         if ($@) {
209                 my $message = $@; 
210                 chomp($message); 
211                 info("collectd: $message");
212                 return -1; 
213         }
214 }
215
216 1;
217
218 # vim: syntax=perl sw=4 ts=4 noet shiftround