|
1 #! /usr/bin/perl |
|
2 # $Id$ |
|
3 # $URL$ |
|
4 |
|
5 use strict; |
|
6 use warnings; |
|
7 use File::Basename; |
|
8 use Getopt::Long; |
|
9 use Fatal qw(:void open flock seek truncate); |
|
10 use Fcntl qw(:flock); |
|
11 use if $ENV{DEBUG} => "Smart::Comments"; |
|
12 use Storable; |
|
13 |
|
14 my $ME = basename $0; |
|
15 my $STATE_DIR = "/var/lib/nagios3/$ME"; |
|
16 my $STATE_FILE = "$STATE_DIR/state"; |
|
17 my $CACHE_FILE = "$STATE_DIR/exim"; |
|
18 our $VERSION = "1.0"; |
|
19 |
|
20 delete @ENV{ grep /PATH/, keys %ENV }; |
|
21 $ENV{PATH} = "/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin"; |
|
22 |
|
23 my %TRANSPORT = (lmtp => "local", |
|
24 pipe => "local", |
|
25 appendfile => "local", |
|
26 autoreply => "local", |
|
27 smtp => "remote", |
|
28 ); |
|
29 |
|
30 my %opt = (init => undef, |
|
31 warn => undef, |
|
32 crit => undef, |
|
33 update => 1, |
|
34 ); |
|
35 |
|
36 my %exim; |
|
37 my %state = (time => 0, |
|
38 inode => { mainlog => 0 }, |
|
39 offset => { mainlog => 0 }, |
|
40 counter => { transport => {}, |
|
41 protocol => {}, |
|
42 in => { local => 0, remote => 0 }, |
|
43 out => { local => 0, remote => 0 }, |
|
44 }, |
|
45 ); |
|
46 |
|
47 sub init(); |
|
48 |
|
49 MAIN: { |
|
50 |
|
51 GetOptions("init=s" => \$opt{init}, |
|
52 "warn=s" => \$opt{warn}, # rate mails/h |
|
53 "crit=s" => \$opt{crit}, # rate mails/h |
|
54 "update!" => \$opt{update}, |
|
55 ) or die "Bad Usage\n"; |
|
56 |
|
57 $opt{init} and do { init(); exit }; |
|
58 |
|
59 # read the exim (cache) file and the status |
|
60 die "$0: $CACHE_FILE not found ($!). Try $0 --help\n" |
|
61 if not -r $CACHE_FILE; |
|
62 die "$0: $STATE_FILE not found ($!). Try $0 --help\n" |
|
63 if not -r $STATE_FILE; |
|
64 %exim = %{ $_ = retrieve($CACHE_FILE) }; |
|
65 %state = %{ $_ = retrieve($STATE_FILE) }; |
|
66 |
|
67 ### %exim |
|
68 ### %state |
|
69 |
|
70 { |
|
71 my @files; |
|
72 |
|
73 # it's important to have the real mainlog last! |
|
74 if ($state{inode}{mainlog} == 0) { |
|
75 ### never seen an inode |
|
76 push @files, [ $exim{mainlog} => 0 ]; |
|
77 } |
|
78 elsif ($state{inode}{mainlog} != (stat $exim{mainlog})[1]) { |
|
79 ### inum changed |
|
80 if ((my $moved_mainlog) |
|
81 = grep { (stat)[1] == $state{inode}{mainlog} } |
|
82 glob(dirname($exim{mainlog}) . "/*")) |
|
83 { |
|
84 ### found old one: $moved_mainlog |
|
85 push @files, [ $moved_mainlog => $state{offset}{mainlog} ]; |
|
86 } |
|
87 push @files, [ $exim{mainlog} => 0 ]; |
|
88 } |
|
89 else { |
|
90 push @files, [ $exim{mainlog} => $state{offset}{mainlog} ]; |
|
91 } |
|
92 |
|
93 my $now = time(); |
|
94 my $dt = $now - $state{time}; |
|
95 my %out = (local => 0, remote => 0); |
|
96 my %in = (local => 0, remote => 0); |
|
97 my (%transport, %protocol); |
|
98 FILE: foreach (@files) { |
|
99 my ($file, $offset) = @$_; |
|
100 open(my $fh, $file) or do { |
|
101 warn "open $file: $!\n"; |
|
102 next FILE; |
|
103 }; |
|
104 seek($fh, $offset, 0) or do { |
|
105 warn "seek $file to offset $offset: $!\n"; |
|
106 next FILE; |
|
107 }; |
|
108 while (<$fh>) { |
|
109 if (/^\S+ \S+ (?:\S+ )?\S+ ?> .* T=(\S+)/) { |
|
110 $transport{$1}++; |
|
111 $out{ $exim{transport}{$1} }++; |
|
112 next; |
|
113 } |
|
114 if (/^\S+ \S+ (?:\S+ )?\S+ <= .* P=(\S+)/) { |
|
115 $protocol{$1}++; |
|
116 $in{ $1 =~ /smtp/ ? "remote" : "local" }++; |
|
117 next; |
|
118 } |
|
119 } |
|
120 |
|
121 # will then hold the values from the *last*, that's why |
|
122 # above I said that the order matters |
|
123 $state{offset}{mainlog} = tell($fh); |
|
124 $state{inode}{mainlog} = (stat $fh)[1]; |
|
125 } |
|
126 |
|
127 # foreach (keys %transport) { |
|
128 # print "$_: $transport{$_}\n"; |
|
129 # } |
|
130 # print "\n"; |
|
131 |
|
132 # save status - status contains absolute counters |
|
133 $state{time} = $now; |
|
134 $state{counter}{in}{remote} += $in{remote}; |
|
135 $state{counter}{in}{local} += $in{local}; |
|
136 $state{counter}{out}{remote} += $out{remote}; |
|
137 $state{counter}{out}{local} += $out{local}; |
|
138 |
|
139 foreach (keys %transport) { |
|
140 $state{counter}{transport}{$_} = 0 |
|
141 if not defined $state{counter}{transport}{$_}; |
|
142 $state{counter}{transport}{$_} += $transport{$_}; |
|
143 } |
|
144 |
|
145 foreach (keys %protocol) { |
|
146 $state{counter}{protocol}{$_} = 0 |
|
147 if not defined $state{counter}{protocol}{$_}; |
|
148 $state{counter}{protocol}{$_} += $protocol{$_}; |
|
149 } |
|
150 |
|
151 store \%state, $STATE_FILE if $opt{update}; |
|
152 |
|
153 # start processing |
|
154 my $in_tot = $in{local} + $in{remote}; |
|
155 my $out_tot = $out{local} + $out{remote}; |
|
156 |
|
157 # mails / minute |
|
158 my $rate_in_remote = $in{remote} / $dt * 60; |
|
159 my $rate_out_remote = $out{remote} / $dt * 60; |
|
160 my $rate_in_local = $in{local} / $dt * 60; |
|
161 my $rate_out_local = $out{local} / $dt * 60; |
|
162 my $rate_in = $in_tot / $dt * 60; |
|
163 my $rate_out = $out_tot / $dt * 60; |
|
164 |
|
165 my $perfdata = "in_local=$state{counter}{in}{local}" |
|
166 . " in_remote=$state{counter}{in}{remote}" |
|
167 . " out_local=$state{counter}{out}{local}" |
|
168 . " out_remote=$state{counter}{out}{remote}"; |
|
169 |
|
170 foreach (keys %{ $state{counter}{transport} }) { |
|
171 $perfdata .= " $_=$state{counter}{transport}{$_}"; |
|
172 } |
|
173 |
|
174 foreach (keys %{ $state{counter}{protocol} }) { |
|
175 $perfdata .= " $_=$state{counter}{protocol}{$_}"; |
|
176 } |
|
177 |
|
178 printf "OK i:%.1f(%.1f/%.1f) o:%.1f(%.1f/%.1f)|$perfdata\n", $rate_in, |
|
179 $rate_in_local, $rate_in_remote, $rate_out, $rate_out_local, |
|
180 $rate_out_remote; |
|
181 |
|
182 } |
|
183 |
|
184 exit 0; |
|
185 |
|
186 } |
|
187 |
|
188 sub init() { |
|
189 |
|
190 # noch sind wir root (hoffentlich) |
|
191 -d $STATE_DIR or do { |
|
192 require File::Path; |
|
193 import File::Path; |
|
194 umask(022); |
|
195 mkpath($STATE_DIR, 0, 0777); |
|
196 }; |
|
197 |
|
198 my $uid = (getpwnam $opt{init})[2]; |
|
199 chown($uid, -1, $STATE_DIR) |
|
200 or die "Can't chown $STATE_DIR to $opt{init} ($uid): $!\n"; |
|
201 |
|
202 umask 077; |
|
203 $> = $uid; # EUID |
|
204 |
|
205 # exim (logfile names) acquired from exim |
|
206 my (%exim, @exim); |
|
207 @exim = `exim -bP all` or `exim4 -bP all`; |
|
208 |
|
209 my ($log) = (split " ", (grep /^\s*log_file_path\s*=/, @exim)[0])[2]; |
|
210 |
|
211 %exim = (mainlog => sprintf($log, "main"), |
|
212 rejectlog => sprintf($log, "reject"), |
|
213 paniclog => sprintf($log, "paniclog"), |
|
214 ); |
|
215 |
|
216 { |
|
217 local $/ = ""; |
|
218 @exim = `exim -bP transports` or `exim4 -bP transports`; |
|
219 foreach (@exim) { |
|
220 my ($transport, $driver) = /^(\S+) transport:.*^driver = (\S+)/ms; |
|
221 |
|
222 # map the transport names to their driver and this to "local/remote" |
|
223 $exim{transport}{$transport} = $TRANSPORT{$driver}; |
|
224 } |
|
225 |
|
226 } |
|
227 |
|
228 store \%exim, $CACHE_FILE; |
|
229 store \%state, $STATE_FILE; |
|
230 |
|
231 } |
|
232 |
|
233 __END__ |
|
234 |
|
235 my $FILE = "/var/log/exim4/mainlog"; |
|
236 my $STATUS = "/var/run/nagios3/$ME/%s"; |
|
237 |
|
238 sub unkn(@) { print "UNKNOWN @_\n"; exit 3 } |
|
239 sub crit(@) { print "CRIT @_\n"; exit 2 } |
|
240 sub warn(@) { print "WARN @_\n"; exit 1 } |
|
241 sub ok(@) { print "OK @_\n"; exit 0 } |
|
242 |
|
243 open(my $log, $FILE) or critical("file $FILE not found"); |
|
244 |
|
245 __END__ |
|
246 # vim:sts=4 sw=4 aw ai sm: |