1#!/usr/local/bin/perl -T 2 3#------------------------------------------------------------------------------ 4# This program implements a SNMP AgentX (RFC 2741) subagent for amavisd-new. 5# 6# Author: Mark Martinec <Mark.Martinec@ijs.si> 7# 8# Copyright (c) 2009-2014, Mark Martinec 9# All rights reserved. 10# 11# Redistribution and use in source and binary forms, with or without 12# modification, are permitted provided that the following conditions 13# are met: 14# 1. Redistributions of source code must retain the above copyright notice, 15# this list of conditions and the following disclaimer. 16# 2. Redistributions in binary form must reproduce the above copyright notice, 17# this list of conditions and the following disclaimer in the documentation 18# and/or other materials provided with the distribution. 19# 20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 24# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30# POSSIBILITY OF SUCH DAMAGE. 31# 32# The views and conclusions contained in the software and documentation are 33# those of the authors and should not be interpreted as representing official 34# policies, either expressed or implied, of the Jozef Stefan Institute. 35 36# (the above license is the 2-clause BSD license, also known as 37# a "Simplified BSD License", and pertains to this program only) 38# 39# Patches and problem reports are welcome. 40# The latest version of this program is available at: 41# http://www.ijs.si/software/amavisd/ 42#------------------------------------------------------------------------------ 43 44package AmavisAgent; 45 46use strict; 47use re 'taint'; 48use warnings; 49use warnings FATAL => qw(utf8 void); 50no warnings 'uninitialized'; 51 52use Errno qw(ESRCH ENOENT EACCES EEXIST); 53use POSIX (); 54use Time::HiRes (); 55use IO::File qw(O_RDONLY O_WRONLY O_RDWR O_CREAT O_EXCL); 56use Unix::Syslog qw(:macros :subs); 57use BerkeleyDB; 58 59use vars qw($VERSION); $VERSION = 1.007; 60 61use vars qw($myversion $myproduct_name $myversion_id $myversion_date); 62$myproduct_name = 'amavis-agentx'; 63$myversion_id = '1.7'; $myversion_date = '20140127'; 64$myversion = "$myproduct_name-$myversion_id ($myversion_date)"; 65my($agent_name) = $myproduct_name; 66 67use vars qw($syslog_ident $syslog_facility); 68$syslog_ident = $myproduct_name; 69$syslog_facility = LOG_MAIL; 70 71my($db_home) = # DB databases directory 72 defined $ENV{'AMAVISD_DB_HOME'} ? $ENV{'AMAVISD_DB_HOME'} : '/var/amavis/db'; 73 74my($mta_queue_dir); 75 76my($top) = '1.3.6.1.4.1.15312.2.1'; 77my(@databases) = ( 78 { root_oid_str => "$top.1", name => 'am.snmp', file => 'snmp.db' }, 79 { root_oid_str => "$top.2", name => 'am.nanny', file => 'nanny.db' }, 80 { root_oid_str => "$top.3.1.1", name => 'pf.maildrop', file => 'maildrop', 81 ttl => 18 }, 82 { root_oid_str => "$top.3.1.2", name => 'pf.incoming', file => 'incoming', 83 ttl => 18 }, 84 { root_oid_str => "$top.3.1.3", name => 'pf.active', file => 'active', 85 ttl => 18 }, 86 { root_oid_str => "$top.3.1.4", name => 'pf.deferred', file => 'deferred', 87 ttl => 18 }, 88); 89 90# 1.3.6.1.4.1.15312 enterprises . Jozef Stefan Institute 91# 1.3.6.1.4.1.15312.2 amavisd-new 92# 1.3.6.1.4.1.15312.2.1 amavisd-new SNMP 93# 1.3.6.1.4.1.15312.2.1.1 amavisd-new Statistics 94# 1.3.6.1.4.1.15312.2.1.2 amavisd-new Process status 95# 1.3.6.1.4.1.15312.2.1.3 amavisd-new (a view into MTA queue sizes) 96# 1.3.6.1.4.1.15312.2.2 amavisd-new LDAP Elements 97 98 99my($log_level) = 0; 100my($daemonize) = 1; 101my($pid_filename); # e.g. "/var/run/amavisd-snmp-subagent.pid"; 102 103my($pid_file_created) = 0; 104my($syslog_open) = 0; 105my($num_proc_gone) = 0; 106 107# geometic progression, rounded, 108# common ratio = exp((ln(60)-ln(1))/6) = 1.97860 109my(@age_slots) = ( 110 0.1, 0.2, 0.5, 111 1, 2, 4, 8, 15, 30, # seconds 112 1*60, 2*60, 4*60, 8*60, 15*60, 30*60, # minutes 113 1*3600, 2*3600, 4*3600, 8*3600, 15*3600, 30*3600); # hours 114 115 116package AmavisVariable; 117 118sub new { my($class) = @_; bless [(undef) x 7], $class } 119sub oid { my($self)=shift; !@_ ? $self->[0] : ($self->[0]=shift) } 120sub oidstr { my($self)=shift; !@_ ? $self->[1] : ($self->[1]=shift) } 121sub name { my($self)=shift; !@_ ? $self->[2] : ($self->[2]=shift) } 122sub type { my($self)=shift; !@_ ? $self->[3] : ($self->[3]=shift) } 123sub suffix { my($self)=shift; !@_ ? $self->[4] : ($self->[4]=shift) } 124sub value { my($self)=shift; !@_ ? $self->[5] : ($self->[5]=shift) } 125sub next { my($self)=shift; !@_ ? $self->[6] : ($self->[6]=shift) } 126 127 128package AmavisAgent; 129 130use NetSNMP::OID; 131use NetSNMP::ASN qw(:all); 132use NetSNMP::agent qw(:all); 133use NetSNMP::default_store qw(:all); 134 135my(%oidstr_to_obj); 136my(@oid_sorted_list); 137my($keep_running) = 1; 138my(%variables); 139 140my(%asn_name_to_type) = ( 141 'C32' => ASN_COUNTER, 142 'C64' => ASN_COUNTER64, 143 'G32' => ASN_GAUGE, 144 'INT' => ASN_INTEGER, 145 'I64' => ASN_INTEGER64, 146 'U32' => ASN_UNSIGNED, 147 'U64' => ASN_UNSIGNED64, 148 'STR' => ASN_OCTET_STR, 149 'OID' => ASN_OBJECT_ID, 150 'TIM' => ASN_TIMETICKS, 151); 152 153my(%asn_type_to_full_name) = ( 154 ASN_COUNTER, 'Counter32', 155 ASN_COUNTER64, 'Counter64', 156 ASN_GAUGE, 'Gauge32', 157 ASN_INTEGER, 'Integer32', 158# 'IpAddress', 159 ASN_INTEGER64, 'Integer64', 160 ASN_UNSIGNED, 'Unsigned32', 161 ASN_UNSIGNED64, 'Unsigned64', 162 ASN_OCTET_STR, 'DisplayString', 163 ASN_OBJECT_ID, 'OBJECT IDENTIFIER', 164 ASN_TIMETICKS, 'TimeTicks', 165); 166 167sub do_log($$;@) { 168 my($level,$errmsg,@args) = @_; 169 if ($level <= $log_level) { 170 # treat $errmsg as sprintf format string if additional arguments provided 171 if (@args) { $errmsg = sprintf($errmsg,@args) } 172 if (!$syslog_open) { 173 print STDERR $errmsg."\n"; # ignoring I/O status 174 } else { 175 my($prio) = $level <= -2 ? LOG_ERR 176 : $level <= -1 ? LOG_WARNING 177 : $level <= 0 ? LOG_NOTICE 178 : $level <= 1 ? LOG_INFO 179 : LOG_DEBUG; 180 syslog(LOG_INFO, "%s", $errmsg); 181 } 182 } 183} 184 185# Returns the smallest defined number from the list, or undef 186sub min(@) { 187 my($r) = @_ == 1 && ref($_[0]) ? $_[0] : \@_; # accept list, or a list ref 188 my($m); for (@$r) { $m = $_ if defined $_ && (!defined $m || $_ < $m) } 189 $m; 190} 191 192# Returns the largest defined number from the list, or undef 193sub max(@) { 194 my($r) = @_ == 1 && ref($_[0]) ? $_[0] : \@_; # accept list, or a list ref 195 my($m); for (@$r) { $m = $_ if defined $_ && (!defined $m || $_ > $m) } 196 $m; 197} 198 199# Return untainted copy of a string (argument can be a string or a string ref) 200sub untaint($) { 201 no re 'taint'; 202 my($str); 203 if (defined($_[0])) { 204 local($1); # avoid Perl taint bug: tainted global $1 propagates taintedness 205 $str = $1 if (ref($_[0]) ? ${$_[0]} : $_[0]) =~ /^(.*)\z/s; 206 } 207 $str; 208} 209 210sub declare_variable($$;$$$) { 211 my($oid_str,$name, $typename,$instance_lo,$instance_hi) = @_; 212 $typename = 'C32' if !defined $typename; 213 $instance_lo = 0 if !defined $instance_lo; 214 $instance_hi = $instance_lo if !defined $instance_hi; 215 $instance_hi = $instance_lo if $instance_hi < $instance_lo; 216 my($type) = $asn_name_to_type{$typename}; 217 for my $ind ($instance_lo .. $instance_hi) { 218 my($full_oid_str) = sprintf("%s.%d", $oid_str,$ind); 219 # do_log(5, "declaring variable %s, %s", $full_oid_str, $name); 220 my($var) = AmavisVariable->new; 221 $var->oidstr($full_oid_str); 222 # $var->oid(NetSNMP::OID->new($full_oid_str)); # later 223 $var->type($type); 224 # $var->suffix($suffix); 225 $var->name("$name.$ind"); 226 if (!exists $variables{"$name.$ind"}) { 227 $variables{"$name.$ind"} = $var; 228 } else { 229 # allow an amavisd variable name to map to multiple SNMP variables 230 if (ref $variables{"$name.$ind"} ne 'ARRAY') { 231 $variables{"$name.$ind"} = [ $variables{"$name.$ind"} ]; 232 } 233 push(@{$variables{"$name.$ind"}}, $var); 234 } 235 } 236} 237 238sub set_variable_value($$) { 239 my($name, $value) = @_; 240 my($instance); local($1,$2); 241 if ($name =~ /^(.*)\.(\d+)/) { $name = $1; $instance = $2 } 242 $instance = 0 if !defined $instance; 243 my($v) = $variables{"$name.$instance"}; 244 if (!defined($v)) { 245 do_log(5, "No such variable %s.%s", $name,$instance); 246 } else { 247 my(@var) = ref $v eq 'ARRAY' ? @$v : $v; 248 for my $var (@var) { 249 my($type) = $var->type; 250 if ($name =~ /^TimeElapsed/) { $value = $value/10 } # ms -> 0.01s ticks 251 elsif ($type == ASN_COUNTER || $type == ASN_GAUGE || 252 $type == ASN_INTEGER || $type == ASN_UNSIGNED || 253 $type == ASN_TIMETICKS) { $value = 0+$value } 254 elsif ($type == ASN_COUNTER64 || $type == ASN_INTEGER64 || 255 $type == ASN_UNSIGNED64) { $value = sprintf("%1.0f",$value) } 256 elsif ($type == ASN_OCTET_STR) { $value = "$value" } 257 $var->value($value); 258 } 259 } 260} 261 262sub reset_all_variable_values($) { 263 my($root_oid_str) = @_; 264 while (my($key,$v) = each(%variables)) { 265 my(@var) = ref $v eq 'ARRAY' ? @$v : $v; 266 for my $var (@var) { 267 if (!defined($root_oid_str) || $var->oidstr =~ /^\Q$root_oid_str\E\./) { 268 $var->value(undef); 269 } 270 } 271 } 272} 273 274sub dump_variables() { 275 for my $oid (@oid_sorted_list) { 276 my(@oidlist) = $oid->to_array; 277 my($oidstr) = join('.', @oidlist); 278 my($descr) = ""; 279 my($suffix_sp) = join(' ', @oidlist[9 .. ($#oidlist-1)]); 280 my($var) = $oidstr_to_obj{$oidstr}; 281 my($mib_type_name) = $asn_type_to_full_name{$var->type}; 282 my($name) = $var->name; 283 $name =~ s/\.0\z//; 284 printf STDERR (<<'END', $name, $mib_type_name, $descr, $suffix_sp); 285%s OBJECT-TYPE 286 SYNTAX %s 287 MAX-ACCESS read-only 288 STATUS current 289 DESCRIPTION 290 "%s" 291 ::= { amavis %s } 292 293END 294 } 295} 296 297sub init_data() { 298 while (my($name,$v) = each(%variables)) { 299 my(@var) = ref $v eq 'ARRAY' ? @$v : $v; 300 for my $var (@var) { 301 my($oidstr) = $var->oidstr; 302 $var->oid(NetSNMP::OID->new($oidstr)); 303 $oidstr_to_obj{$oidstr} = $var; 304 } 305 } 306 @oid_sorted_list = sort { snmp_oid_compare($a,$b) } 307 map { $_->oid } 308 map { ref $_ eq 'ARRAY' ? @$_ : $_ } values(%variables); 309 # build a linked list of variable objects in OID sorted order 310 # to speed up sequential MIB traversal by getnext 311 my($prev_var); 312 for my $oid (@oid_sorted_list) { 313 my($oidstr) = join('.', $oid->to_array); 314 my($var) = $oidstr_to_obj{$oidstr}; 315 $prev_var->next($var) if defined $prev_var; 316 $prev_var = $var; 317 } 318} 319 320sub collect_all_db_data($$) { 321 my($database,$values) = @_; 322 my($dbfile) = $database->{file}; 323 my(@dbstat) = stat("$db_home/$dbfile"); 324 my($errn) = @dbstat ? 0 : 0+$!; 325 $errn==0 || $errn==ENOENT or die "stat $db_home/$dbfile: $!"; 326 if (defined $database->{db} && $database->{old_db_inode} != $dbstat[1]) { 327 $database->{db}->db_close==0 328 or die "BDB db_close error: $BerkeleyDB::Error $!"; 329 undef $database->{db}; 330 do_log(1, "Reopening snmp database %s/%s", $db_home,$dbfile); 331 } 332 if (!defined $database->{db} && $errn==0) { 333 reset_all_variable_values($database->{root_oid_str}); 334 $database->{old_db_inode} = $dbstat[1]; 335 $database->{env} = BerkeleyDB::Env->new( 336 -Home => $db_home, -Flags => DB_INIT_CDB | DB_INIT_MPOOL, 337 -ErrFile => \*STDOUT, -Verbose => 1); 338 defined $database->{env} or die "BDB no env: $BerkeleyDB::Error $!"; 339 $database->{db} = BerkeleyDB::Hash->new(-Filename => $dbfile, 340 -Env => $database->{env}); 341 defined $database->{db} or die "BDB no dbS 1: $BerkeleyDB::Error $!"; 342 } 343 my($eval_stat,$interrupt); $interrupt = ''; 344 if (!defined $database->{db}) { 345 do_log(1, "No snmp database %s/%s", $db_home,$dbfile); 346 } else { 347 my($stat,$key,$val); 348 my($h1) = sub { $interrupt = $_[0] }; 349 local(@SIG{qw(INT HUP TERM TSTP QUIT ALRM USR1 USR2)}) = ($h1) x 8; 350 eval { 351 # be as quick as possible while a database is locked by a cursor 352 my($cursor) = $database->{db}->db_cursor; # obtain read lock 353 $database->{cursor} = $cursor; 354 defined $cursor or die "db_cursor error: $BerkeleyDB::Error"; 355 while ( ($stat=$cursor->c_get($key,$val,DB_NEXT)) == 0 ) { 356 $values->{$key} = $val; 357 } 358 $stat==DB_NOTFOUND or die "c_get: $BerkeleyDB::Error $!"; 359 $cursor->c_close==0 or die "c_close error: $BerkeleyDB::Error"; 360 undef $database->{cursor}; 361 1; 362 } or do { 363 $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat; 364 }; 365 if (defined $database->{db}) { 366 # unlock, ignoring status 367 $database->{cursor}->c_close if defined $database->{cursor}; 368 undef $database->{cursor}; 369 } 370 } 371 if ($interrupt ne '') { kill($interrupt,$$) } # resignal 372 elsif ($eval_stat ne '') { chomp($eval_stat); die "BDB $eval_stat\n" } 373} 374 375sub count_files_in_postfix_dir($$); # prototype 376sub count_files_in_postfix_dir($$) { 377 my($dir,$deadline) = @_; 378 local(*DIR); my($f); my($cnt) = 0; my($aborted) = 0; 379 if (!opendir(DIR,$dir)) { 380 do_log(-1, "Can't open directory %s: %s", $dir,$!); 381 } else { 382 while (defined($f = readdir(DIR))) { 383 next if $f eq '.' || $f eq '..'; 384 # Postfix uses one-character subdirs 385 if (length($f) == 1 && -d "$dir/$f") { 386 my($n,$abt) = count_files_in_postfix_dir("$dir/$f", $deadline); 387 $cnt += $n; 388 if ($abt) { $aborted = 1; last } 389 } else { 390 $cnt++; 391 } 392 if (defined $deadline && Time::HiRes::time > $deadline) { 393 $aborted = 1; last; 394 } 395 } 396 closedir(DIR) or die "Error closing directory $dir: $!"; 397 } 398 ($cnt,$aborted); 399} 400 401sub update_data($) { 402 my($database) = @_; 403 do_log(3, "updating variables from %s", $database->{name}); 404 my($start_time) = Time::HiRes::time; 405 if ($database->{name} =~ /^pf/) { 406 # not really a database file, just a 'view' into an MTA spool directory 407 my($dir) = $database->{file}; 408 my($cnt,$aborted) = 409 count_files_in_postfix_dir("$mta_queue_dir/$dir", $start_time + 5); 410 my($var_name) = "MtaQueueEntries\u$dir"; 411 set_variable_value($var_name, $cnt); 412 do_log(3, "mta queue: %s %d", $var_name,$cnt); 413 do_log(-1,"exceeded time limit on dir %s, aborted after %.1f s, ". 414 "count so far: %d", 415 $var_name, Time::HiRes::time - $start_time, $cnt) if $aborted; 416 } elsif ($database->{file} eq 'snmp.db') { 417 my(%values); collect_all_db_data($database,\%values); 418 while (my($key,$val) = each(%values)) { 419 next if $key =~ /\.byOS\./s; 420 next if $key =~ /^virus\.byname\./s; 421 next if $key =~ /^(?:OpsDecType|OpsDecBy|OpsSql)/s; 422 next if $key =~ /^entropy/s; 423 next if $key =~ /^ContentBadHdr/ && $key !~ /^ContentBadHdrMsgs/; 424 local($1); 425 if ($val =~ /^(?:C32|C64) (.*)\z/) { 426 set_variable_value($key, 0+$1); 427 } elsif ($val =~ /^STR (.*)\z/) { 428 set_variable_value($key, "$1"); 429 } elsif ($key eq 'sysUpTime' && $val =~ /^INT (.*)\z/) { 430 my($uptime) = $start_time - $1; my($ticks) = int($uptime*100); 431 set_variable_value($key, $ticks); 432 } elsif ($val =~ /^(?:G32|INT|I64|U32|U64|TIM) (.*)\z/) { 433 set_variable_value($key, 0+$1); 434 } elsif ($val =~ /^OID (.*)\z/) { 435 set_variable_value($key, $1); 436 } else { 437 # set_variable_value($key, $val); 438 } 439 } 440 } elsif ($database->{file} eq 'nanny.db') { 441 my(%values); collect_all_db_data($database,\%values); 442 my(%proc_timestamp, %proc_state, %proc_task_id); 443 while (my($key,$val) = each(%values)) { 444 local($1,$2); 445 if ($val !~ /^(\d+(?:\.\d*)?) (.*?) *\z/s) { 446 do_log(0, "Bad %s db entry: %s, %s", $database->{file},$key,$val); 447 } else { 448 $proc_timestamp{$key} = $1; my($task_id) = $2; 449 $proc_state{$key} = $1 if $task_id =~ s/^([^0-9])//; 450 $proc_task_id{$key} = $task_id; 451 } 452 } 453 my(@to_be_removed); my($num_proc_idle) = 0; my($num_proc_busy) = 0; 454 my(@num_proc_busy_by_age); my(%num_proc_busy_by_activity); 455 for my $pid (keys(%proc_timestamp)) { 456 my($idling) = $proc_task_id{$pid} eq '' && 457 $proc_state{$pid} =~ /^[. ]?\z/s; 458 my($age) = $start_time - $proc_timestamp{$pid}; 459 my($n) = kill(0,$pid); # test if the process is still there 460 if ($n == 0 && $! != ESRCH) { 461 do_log(-1, "Can't check the process %s: %s", $pid,$!); 462 } elsif ($n == 0) { # ESRCH means there is no such process 463 push(@to_be_removed, $pid); # process went away! 464 } elsif ($idling) { 465 $num_proc_idle++; 466 } else { # busy for $age seconds 467 $num_proc_busy++; 468 $num_proc_busy_by_age[0]++; 469 my($j) = 1; 470 for my $t (@age_slots) { 471 if ($age >= $t) { $num_proc_busy_by_age[$j]++ } 472 $j++; 473 } 474 my($s) = $proc_state{$pid}; 475 if ($s eq 'm' || $s eq 'd' || $s eq 'F') { $s = 'm' } 476 elsif ($s eq 'D' || $s eq 'V' || $s eq 'S') { } 477 else { $s = ' ' } 478 $num_proc_busy_by_activity{$s}++; 479 } 480 } 481 $num_proc_gone += scalar(@to_be_removed); 482 # all are gauges, except ProcGone 483 set_variable_value('ProcAll', $num_proc_idle+$num_proc_busy); 484 set_variable_value('ProcIdle', $num_proc_idle); 485 set_variable_value('ProcBusy', $num_proc_busy); 486 set_variable_value('ProcGone', $num_proc_gone); # counter! 487 for my $j (0..@age_slots) { # age_slots start at 1, zero is in addition 488 set_variable_value("ProcBusy$j", $num_proc_busy_by_age[$j] || 0); 489 } 490 set_variable_value("ProcBusyTransfer",$num_proc_busy_by_activity{'m'}||0); 491 set_variable_value("ProcBusyDecode", $num_proc_busy_by_activity{'D'}||0); 492 set_variable_value("ProcBusyVirus", $num_proc_busy_by_activity{'V'}||0); 493 set_variable_value("ProcBusySpam", $num_proc_busy_by_activity{'S'}||0); 494 set_variable_value("ProcBusyOther", $num_proc_busy_by_activity{' '}||0); 495 if (@to_be_removed) { # some processes no longer exist, update db 496 my($eval_stat,$interrupt); $interrupt = ''; 497 my($h1) = sub { $interrupt = $_[0] }; 498 local(@SIG{qw(INT HUP TERM TSTP QUIT ALRM USR1 USR2)}) = ($h1) x 8; 499 eval { 500 # obtain a write lock 501 my($cursor) = $database->{db}->db_cursor(DB_WRITECURSOR); 502 $database->{cursor} = $cursor; 503 defined $cursor or die "BDB db_cursor error: $BerkeleyDB::Error"; 504 for my $key (@to_be_removed) { 505 my($val); my($stat) = $cursor->c_get($key,$val,DB_SET); 506 $stat==0 || $stat==DB_NOTFOUND 507 or die "BDB c_get: $BerkeleyDB::Error, $!."; 508 if ($stat==0) { # remove existing entry 509 $cursor->c_del==0 or die "c_del: $BerkeleyDB::Error, $!."; 510 } 511 } 512 $cursor->c_close==0 or die "c_close error: $BerkeleyDB::Error"; 513 undef $database->{cursor}; 514 1; 515 } or do { 516 $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat; 517 }; 518 if (defined $database->{db}) { 519 # unlock, ignoring status 520 $database->{cursor}->c_close if defined $database->{cursor}; 521 undef $database->{cursor}; 522 } 523 } 524 } 525 526 my($now) = Time::HiRes::time; 527 528 my($elapsed) = $now - $start_time; 529 $elapsed = 0 if $elapsed < 0; # clock jump? 530 my($ll) = $elapsed >= 30 ? -1 : $elapsed >= 5 ? 0 : $elapsed >= 1 ? 2 : 3; 531 do_log($ll, "updating %s took %.3f s", $database->{name}, $elapsed); 532 533 my($ttl_lower_bound) = 8*$elapsed; # don't be a hog! 534 my($since_query) = $database->{last_query_timestamp}; 535 $since_query = $now - $since_query if defined $since_query; 536 if (defined $since_query && $elapsed > 4) { 537 # there is a chance that a SNMP client timed out on this query; 538 # stretch the next update period to allow one quick next response 539 # from cached data, assuming queries are at about regular intervals 540 $ttl_lower_bound = max($ttl_lower_bound, 1.5 * $since_query); 541 } 542 $ttl_lower_bound = min($ttl_lower_bound, 20*60); # cap at 20 minutes 543 my($ttl) = $database->{ttl}; 544 $ttl = 4 if !defined $ttl || $ttl <= 0; 545 if ($ttl < $ttl_lower_bound) { 546 $ttl = $ttl_lower_bound; 547 do_log(3, "postponing refresh on %s for another %.1f s%s", 548 $database->{name}, $ttl, 549 !defined $since_query ? '' 550 : sprintf(", %.1f s since query",$since_query) ); 551 } 552 $database->{last_refreshed} = $now; 553 $database->{update_due_at} = $now + $ttl; 554} 555 556sub find_next_gt($$) { 557 my($x, $a_ref) = @_; 558 my($l, $u) = (0, $#$a_ref); 559 my $j; 560 while ($l <= $u) { 561 $j = $l + int(($u - $l)/2); # good practices: avoids integer overflow 562 if ($a_ref->[$j] > $x) { $u = $j-1 } else { $l = $j+1 } 563 } 564 $l > $#$a_ref ? -1 : $l; 565} 566 567my($fast_poll) = 0; 568my($last_query_timestamp) = 0; 569 570sub snmp_handler($$$$) { 571 my($handler, $registration_info, $request_info, $requests) = @_; 572 573 my($now) = Time::HiRes::time; 574 my($dt) = $now - $last_query_timestamp; 575 if ($dt < 1.5) { $fast_poll = 1 } elsif ($dt > 4) { $fast_poll = 0 } 576 $last_query_timestamp = $now; 577 578 my($mode) = $request_info->getMode; 579 for (my $req=$requests; $req; $req=$req->next) { 580 my($oid_in_request) = $req->getOID; # OID from a request 581 my($actual_oid); my($err); my($eom) = 0; 582 if ($mode == MODE_GET) { 583 $actual_oid = $oid_in_request; 584 do_log(5, "Get %s", $oid_in_request); 585 } elsif ($mode == MODE_GETBULK) { 586 # never happens, not registered for getbulk 587 do_log(2, "GetBulk %s", $oid_in_request); 588 } elsif ($mode == MODE_GETNEXT) { 589 if (!@oid_sorted_list) { 590 $eom = 1; # end of MIB 591 } elsif ($oid_in_request < $oid_sorted_list[0]) { 592 $actual_oid = $oid_sorted_list[0]; 593 $req->setOID($actual_oid); 594 do_log(4, "First: %s -> %s", $oid_in_request,$actual_oid); 595 } elsif ($oid_in_request > $oid_sorted_list[-1]) { 596 $eom = 1; # end of MIB 597 do_log(4, "Last: %s", $oid_in_request); 598 } else { 599 # check first for a sequential traversal, likely faster 600 my($var) = $oidstr_to_obj{join('.', $oid_in_request->to_array)}; 601 if ($var) { 602 my($next_var) = $var->next; 603 if (!$next_var) { 604 $eom = 1; # end of MIB 605 } else { 606 $actual_oid = $next_var->oid; 607 $req->setOID($actual_oid); 608 } 609 } 610 if (!$err && !defined $actual_oid) { # fall back to a binary search 611 do_log(5, "Using a binary search for %s", $oid_in_request); 612 my($ind) = find_next_gt($oid_in_request, \@oid_sorted_list); 613 if ($ind < 0) { 614 $eom = 1; # end of MIB 615 } else { 616 $actual_oid = $oid_sorted_list[$ind]; 617 $req->setOID($actual_oid); 618 } 619 } 620 } 621 do_log(5, "GetNext %s -> %s", $oid_in_request, 622 !defined $actual_oid ? 'undef' : $actual_oid); 623 } else { 624 do_log(0, "Unknown request %s", $oid_in_request); 625 $req->setError($request_info, SNMP_ERR_NOTWRITABLE); $err = 1; 626 } 627 if ($err) { 628 # already dealt with 629 } elsif ($eom || !defined $actual_oid) { # end of MIB 630 # just silently not provide a value 631 do_log(5, "No more MIB beyond %s", $oid_in_request); 632 } else { 633 my($oid_str) = join('.', $actual_oid->to_array); 634 my($var) = $oidstr_to_obj{$oid_str}; 635 if (!$var) { 636 $req->setError($request_info, SNMP_ERR_NOSUCHNAME); 637 } else { 638 # find out under which OID root the query falls 639 for my $database (@databases) { 640 next if !$database->{registered}; 641 my($root_oid_str) = $database->{root_oid_str}; 642 if ($oid_str =~ /^\Q$root_oid_str\E\./) { 643 $database->{last_query_timestamp} = $now; 644 if (!defined($database->{update_due_at}) || 645 Time::HiRes::time >= 646 $database->{update_due_at} + ($fast_poll ? 4 : 0) ) { 647 # fast polling stretches time-to-update a bit, increasing 648 # chances of collecting consistent data from the same moment 649 update_data($database); # stale MIB, needs updating 650 } 651 } 652 } 653 my($type, $value, $name) = ($var->type, $var->value, $var->name); 654 if (!defined $type) { 655 $req->setError($request_info, SNMP_ERR_BADVALUE); 656 } else { 657 if (!defined $value) { 658 if ($type == ASN_OCTET_STR) { $value = "" } 659 elsif ($type == ASN_OBJECT_ID) { $value = "0" } 660 elsif ($type == ASN_COUNTER64 || $type == ASN_INTEGER64 || 661 $type == ASN_UNSIGNED64) { $value = "0" } 662 else { $value = 0 } 663 } 664 my($status) = $req->setValue($type,$value); 665 if (!$status) { 666 do_log(0, "setValue error: %s, %s, %s", $type,$name,$value); 667 $req->setError($request_info, SNMP_ERR_BADVALUE); 668 } 669 } 670 } 671 } 672 } 673 1; 674} 675 676sub daemonize() { 677 my($pid); 678 closelog(); $syslog_open = 0; 679 680 STDOUT->autoflush(1); 681 STDERR->autoflush(1); 682 683 # the first fork allows the shell to return and allows doing a setsid 684 eval { $pid = fork(); 1 } 685 or do { 686 my($eval_stat) = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat; 687 die "Error forking #1: $eval_stat"; 688 }; 689 defined $pid or die "Can't fork #1: $!"; 690 if ($pid) { # parent process terminates here 691 POSIX::_exit(0); # avoid END and destructor processing 692 } 693 694 # disassociate from a controlling terminal 695 my($pgid) = POSIX::setsid(); 696 defined $pgid && $pgid >= 0 or die "Can't start a new session: $!"; 697 698 # We are now a session leader. As a session leader, opening a file 699 # descriptor that is a terminal will make it our controlling terminal. 700 # The second fork makes us NOT a session leader. Only session leaders 701 # can acquire a controlling terminal, so we may now open up any file 702 # we wish without worrying that it will become a controlling terminal. 703 704 # second fork prevents from accidentally reacquiring a controlling terminal 705 eval { $pid = fork(); 1 } 706 or do { 707 my($eval_stat) = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat; 708 die "Error forking #2: $eval_stat"; 709 }; 710 defined $pid or die "Can't fork #2: $!"; 711 if ($pid) { # parent process terminates here 712 POSIX::_exit(0); # avoid END and destructor processing 713 } 714 715 # a daemonized child process, live long and prosper... 716 do_log(2, "Daemonized as process [%s]", $$); 717 718 chdir('/') or die "Can't chdir to '/': $!"; 719 720 openlog($syslog_ident, LOG_PID | LOG_NDELAY, $syslog_facility); 721 $syslog_open = 1; 722 723 close(STDIN) or die "Can't close STDIN: $!"; 724 close(STDOUT) or die "Can't close STDOUT: $!"; 725 open(STDIN, '</dev/null') or die "Can't open /dev/null: $!"; 726 open(STDOUT, '>/dev/null') or die "Can't open /dev/null: $!"; 727 close(STDERR) or die "Can't close STDERR: $!"; 728 open(STDERR, '>&STDOUT') or die "Can't dup STDOUT: $!"; 729} 730 731sub usage() { 732 return <<"EOD"; 733Usage: 734 $0 [options] 735 736 Options: 737 -V show version, then exit 738 -h show help, then exit 739 -f stay in foreground 740 -d log_level debugging level, 0..5, default 0 741 -P pid_file a file name to receive a PID of a damonized process 742 -D db_home_dir amavis database directory ($db_home), 743 default AMAVISD_DB_HOME or /var/amavis/db 744EOD 745} 746 747 748# main program starts here 749 750 delete @ENV{'PATH', 'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; 751 $SIG{INT} = sub { die "interrupted\n" }; # do the END code block 752 $SIG{TERM} = sub { die "terminated\n" }; # do the END code block 753 $SIG{PIPE} = 'IGNORE'; # don't signal on a write to a widowed pipe 754 755 while (@ARGV >= 2 && $ARGV[0] =~ /^-[dDP]\z/ || 756 @ARGV >= 1 && $ARGV[0] =~ /^-[hVf-]\z/) { 757 my($opt,$val); 758 $opt = shift @ARGV; 759 $val = shift @ARGV if $opt !~ /^-[hVf-]\z/; # these take no arguments 760 if ($opt eq '--') { 761 last; 762 } elsif ($opt eq '-h') { # -h (help) 763 die "$myversion\n\n" . usage(); 764 } elsif ($opt eq '-V') { # -V (version) 765 die "$myversion\n"; 766 } elsif ($opt eq '-f') { # -f (foreground) 767 $daemonize = 0; 768 } elsif ($opt eq '-d') { # -d log_level 769 $log_level = 0+$val; 770 } elsif ($opt eq '-D') { # -D db_home_dir, empty string turns off db use 771 $db_home = untaint($val) if $val ne ''; 772 } elsif ($opt eq '-P') { # -P pid_file 773 $pid_filename = untaint($val) if $val ne ''; 774 } else { 775 die "Error in parsing command line options: $opt\n\n" . usage(); 776 } 777 } 778 !@ARGV or die "Unprocessed command line options: $ARGV[0]\n\n" . usage(); 779 780 if (!defined $mta_queue_dir) { # test for access to Postfix queue directory 781 local($ENV{PATH}) = '/usr/sbin:/usr/local/sbin:/opt/postfix/sbin'; 782 $! = 0; 783 $mta_queue_dir = qx(postconf -h queue_directory); 784 if (!defined $mta_queue_dir) { 785 if ($! != 0) { 786 do_log(1, "no postfix (unable to run postconf command): $!"); 787 } else { 788 do_log(1, "failed to execute \"postconf queue_directory\": $?"); 789 } 790 } else { 791 chomp $mta_queue_dir; 792 if ($mta_queue_dir =~ /^\s*\z/) { 793 do_log(1, "unknown Postfix queue directory"); 794 undef $mta_queue_dir; 795 } else { 796 do_log(2, "got a Postfix queue directory: %s", $mta_queue_dir); 797 my($dir) = "$mta_queue_dir/active"; 798 local(*DIR); 799 if (!opendir(DIR,$dir)) { # testing access 800 do_log(1, "can't open directory %s: %s", $dir,$!); 801 undef $mta_queue_dir; 802 } else { 803 closedir(DIR) or die "Error closing directory $dir: $!"; 804 } 805 } 806 } 807 } 808 809 { # amavisd statistics MIB 810 my($r) = $databases[0]->{root_oid_str}; 811 declare_variable("$r.1.1", 'sysDescr', 'STR'); 812 declare_variable("$r.1.2", 'sysObjectID', 'OID'); 813 declare_variable("$r.1.3", 'sysUpTime', 'TIM'); 814 declare_variable("$r.1.4", 'sysContact', 'STR'); 815 declare_variable("$r.1.5", 'sysName', 'STR'); 816 declare_variable("$r.1.6", 'sysLocation', 'STR'); 817 declare_variable("$r.1.7", 'sysServices', 'INT'); 818 819 declare_variable("$r.2.1", 'InMsgs'); # orig=x locl=x 820 declare_variable("$r.2.2", 'InMsgsInbound'); # orig=0 locl=1 821 declare_variable("$r.2.3", 'InMsgsOutbound'); # orig=1 locl=0 822 declare_variable("$r.2.4", 'InMsgsInternal'); # orig=1 locl=1 823 declare_variable("$r.2.5", 'InMsgsOriginating'); # orig=1 locl=x 824 declare_variable("$r.2.6", 'InMsgsOpenRelay'); # orig=0 locl=0 825 826 # these have duplicates at $r.{19..26}.1, except InMsgsStatusRelayed 827 declare_variable("$r.2.7", 'InMsgsStatusAccepted'); # 2xx, AM.PDP 828 declare_variable("$r.2.8", 'InMsgsStatusRelayed'); # 2xx, forward 829 declare_variable("$r.2.9", 'InMsgsStatusDiscarded'); # 2xx, no DSN 830 declare_variable("$r.2.10", 'InMsgsStatusNoBounce'); # 2xx, no DSN 831 declare_variable("$r.2.11", 'InMsgsStatusBounced'); # 2xx, DSN sent 832 declare_variable("$r.2.12", 'InMsgsStatusRejected'); # 5xx 833 declare_variable("$r.2.13", 'InMsgsStatusTempFailed'); # 4xx 834 835 declare_variable("$r.3.1", 'InMsgsSize', 'C64'); 836 declare_variable("$r.3.2", 'InMsgsSizeInbound', 'C64'); 837 declare_variable("$r.3.3", 'InMsgsSizeOutbound', 'C64'); 838 declare_variable("$r.3.4", 'InMsgsSizeInternal', 'C64'); 839 declare_variable("$r.3.5", 'InMsgsSizeOriginating', 'C64'); 840 declare_variable("$r.3.6", 'InMsgsSizeOpenRelay', 'C64'); 841 842 declare_variable("$r.4.1", 'InMsgsRecips'); # orig=x locl=x 843 declare_variable("$r.4.2", 'InMsgsRecipsInbound'); # orig=0 locl=1 844 declare_variable("$r.4.3", 'InMsgsRecipsOutbound'); # orig=1 locl=0 845 declare_variable("$r.4.4", 'InMsgsRecipsInternal'); # orig=1 locl=1 846 declare_variable("$r.4.5", 'InMsgsRecipsOriginating'); # orig=1 locl=x 847 declare_variable("$r.4.6", 'InMsgsRecipsOpenRelay'); # orig=0 locl=0 848 declare_variable("$r.4.7", 'InMsgsRecipsLocal'); # orig=x locl=1 849 850 declare_variable("$r.5.1", 'InMsgsBounce'); 851 declare_variable("$r.5.2", 'InMsgsBounceNullRPath'); 852 declare_variable("$r.5.3", 'InMsgsBounceKilled'); 853 declare_variable("$r.5.4", 'InMsgsBounceUnverifiable'); 854 declare_variable("$r.5.5", 'InMsgsBounceRescuedByDomain'); 855 declare_variable("$r.5.6", 'InMsgsBounceRescuedByOriginating'); 856 declare_variable("$r.5.7", 'InMsgsBounceRescuedByPenPals'); 857 858 declare_variable("$r.6.1", 'OutMsgs'); 859 declare_variable("$r.6.2", 'OutMsgsRelay'); 860 declare_variable("$r.6.3", 'OutMsgsSubmit'); 861 declare_variable("$r.6.4", 'OutMsgsSubmitQuar'); 862 declare_variable("$r.6.5", 'OutMsgsSubmitDsn'); 863 declare_variable("$r.6.6", 'OutMsgsSubmitNotif'); 864 declare_variable("$r.6.7", 'OutMsgsSubmitAV'); 865 declare_variable("$r.6.8", 'OutMsgsSubmitArf'); 866 declare_variable("$r.6.9", 'OutMsgsProtoLocal'); 867 declare_variable("$r.6.10", 'OutMsgsProtoLocalRelay'); 868 declare_variable("$r.6.11", 'OutMsgsProtoLocalSubmit'); 869 declare_variable("$r.6.12", 'OutMsgsProtoSMTP'); 870 declare_variable("$r.6.13", 'OutMsgsProtoSMTPRelay'); 871 declare_variable("$r.6.14", 'OutMsgsProtoSMTPSubmit'); 872 declare_variable("$r.6.15", 'OutMsgsProtoLMTP'); 873 declare_variable("$r.6.16", 'OutMsgsProtoLMTPRelay'); 874 declare_variable("$r.6.17", 'OutMsgsProtoLMTPSubmit'); 875 declare_variable("$r.6.18", 'OutMsgsProtoBSMTP'); 876 declare_variable("$r.6.19", 'OutMsgsProtoBSMTPRelay'); 877 declare_variable("$r.6.20", 'OutMsgsProtoBSMTPSubmit'); 878 declare_variable("$r.6.21", 'OutMsgsProtoPipe'); 879 declare_variable("$r.6.22", 'OutMsgsProtoPipeRelay'); 880 declare_variable("$r.6.23", 'OutMsgsProtoPipeSubmit'); 881 declare_variable("$r.6.24", 'OutMsgsProtoSQL'); 882 declare_variable("$r.6.25", 'OutMsgsProtoSQLRelay'); 883 declare_variable("$r.6.26", 'OutMsgsProtoSQLSubmit'); 884 declare_variable("$r.6.27", 'OutMsgsDelivers'); # 2xx 885 declare_variable("$r.6.28", 'OutMsgsAttemptFails'); # 4xx 886 declare_variable("$r.6.29", 'OutMsgsRejects'); # 5xx 887 888 declare_variable("$r.7.1", 'OutMsgsSize', 'C64'); 889 declare_variable("$r.7.2", 'OutMsgsSizeRelay', 'C64'); 890 declare_variable("$r.7.3", 'OutMsgsSizeSubmit', 'C64'); 891 declare_variable("$r.7.4", 'OutMsgsSizeSubmitQuar', 'C64'); 892 declare_variable("$r.7.5", 'OutMsgsSizeSubmitDsn', 'C64'); 893 declare_variable("$r.7.6", 'OutMsgsSizeSubmitNotif', 'C64'); 894 declare_variable("$r.7.7", 'OutMsgsSizeSubmitAV', 'C64'); 895 declare_variable("$r.7.8", 'OutMsgsSizeSubmitArf', 'C64'); 896 declare_variable("$r.7.9", 'OutMsgsSizeProtoLocal', 'C64'); 897 declare_variable("$r.7.10", 'OutMsgsSizeProtoLocalRelay', 'C64'); 898 declare_variable("$r.7.11", 'OutMsgsSizeProtoLocalSubmit','C64'); 899 declare_variable("$r.7.12", 'OutMsgsSizeProtoSMTP', 'C64'); 900 declare_variable("$r.7.13", 'OutMsgsSizeProtoSMTPRelay', 'C64'); 901 declare_variable("$r.7.14", 'OutMsgsSizeProtoSMTPSubmit', 'C64'); 902 declare_variable("$r.7.15", 'OutMsgsSizeProtoLMTP', 'C64'); 903 declare_variable("$r.7.16", 'OutMsgsSizeProtoLMTPRelay', 'C64'); 904 declare_variable("$r.7.17", 'OutMsgsSizeProtoLMTPSubmit', 'C64'); 905 declare_variable("$r.7.18", 'OutMsgsSizeProtoBSMTP', 'C64'); 906 declare_variable("$r.7.19", 'OutMsgsSizeProtoBSMTPRelay', 'C64'); 907 declare_variable("$r.7.20", 'OutMsgsSizeProtoBSMTPSubmit','C64'); 908 declare_variable("$r.7.21", 'OutMsgsSizeProtoPipe', 'C64'); 909 declare_variable("$r.7.22", 'OutMsgsSizeProtoPipeRelay', 'C64'); 910 declare_variable("$r.7.23", 'OutMsgsSizeProtoPipeSubmit', 'C64'); 911 declare_variable("$r.7.24", 'OutMsgsSizeProtoSQL', 'C64'); 912 declare_variable("$r.7.25", 'OutMsgsSizeProtoSQLRelay', 'C64'); 913 declare_variable("$r.7.26", 'OutMsgsSizeProtoSQLSubmit', 'C64'); 914 915 declare_variable("$r.8.1", 'QuarMsgs'); 916 declare_variable("$r.8.2", 'QuarMsgsArch'); 917 declare_variable("$r.8.3", 'QuarMsgsClean'); 918 declare_variable("$r.8.4", 'QuarMsgsMtaFailed'); 919 declare_variable("$r.8.5", 'QuarMsgsOversized'); 920 declare_variable("$r.8.6", 'QuarMsgsBadHdr'); 921 declare_variable("$r.8.7", 'QuarMsgsSpammy'); 922 declare_variable("$r.8.8", 'QuarMsgsSpam'); 923 declare_variable("$r.8.9", 'QuarMsgsUnchecked'); 924 declare_variable("$r.8.10", 'QuarMsgsBanned'); 925 declare_variable("$r.8.11", 'QuarMsgsVirus'); 926 declare_variable("$r.8.12", 'QuarAttemptTempFails'); 927 declare_variable("$r.8.13", 'QuarAttemptFails'); 928 929 declare_variable("$r.9.1", 'QuarMsgsSize', 'C64'); 930 declare_variable("$r.9.2", 'QuarMsgsSizeArch', 'C64'); 931 declare_variable("$r.9.3", 'QuarMsgsSizeClean', 'C64'); 932 declare_variable("$r.9.4", 'QuarMsgsSizeMtaFailed', 'C64'); 933 declare_variable("$r.9.5", 'QuarMsgsSizeOversized', 'C64'); 934 declare_variable("$r.9.6", 'QuarMsgsSizeBadHdr', 'C64'); 935 declare_variable("$r.9.7", 'QuarMsgsSizeSpammy', 'C64'); 936 declare_variable("$r.9.8", 'QuarMsgsSizeSpam', 'C64'); 937 declare_variable("$r.9.9", 'QuarMsgsSizeUnchecked', 'C64'); 938 declare_variable("$r.9.10", 'QuarMsgsSizeBanned', 'C64'); 939 declare_variable("$r.9.11", 'QuarMsgsSizeVirus', 'C64'); 940 941 declare_variable("$r.10.1.1", 'ContentCleanMsgs'); 942 declare_variable("$r.10.1.2", 'ContentCleanMsgsInbound'); 943 declare_variable("$r.10.1.3", 'ContentCleanMsgsOutbound'); 944 declare_variable("$r.10.1.4", 'ContentCleanMsgsInternal'); 945 declare_variable("$r.10.1.5", 'ContentCleanMsgsOriginating'); 946 declare_variable("$r.10.1.6", 'ContentCleanMsgsOpenRelay'); 947 948 declare_variable("$r.10.2.1", 'ContentMtaFailedMsgs'); 949 declare_variable("$r.10.2.2", 'ContentMtaFailedMsgsInbound'); 950 declare_variable("$r.10.2.3", 'ContentMtaFailedMsgsOutbound'); 951 declare_variable("$r.10.2.4", 'ContentMtaFailedMsgsInternal'); 952 declare_variable("$r.10.2.5", 'ContentMtaFailedMsgsOriginating'); 953 declare_variable("$r.10.2.6", 'ContentMtaFailedMsgsOpenRelay'); 954 955 declare_variable("$r.10.3.1", 'ContentOversizedMsgs'); 956 declare_variable("$r.10.3.2", 'ContentOversizedMsgsInbound'); 957 declare_variable("$r.10.3.3", 'ContentOversizedMsgsOutbound'); 958 declare_variable("$r.10.3.4", 'ContentOversizedMsgsInternal'); 959 declare_variable("$r.10.3.5", 'ContentOversizedMsgsOriginating'); 960 declare_variable("$r.10.3.6", 'ContentOversizedMsgsOpenRelay'); 961 962 declare_variable("$r.10.4.1", 'ContentBadHdrMsgs'); 963 declare_variable("$r.10.4.2", 'ContentBadHdrMsgsInbound'); 964 declare_variable("$r.10.4.3", 'ContentBadHdrMsgsOutbound'); 965 declare_variable("$r.10.4.4", 'ContentBadHdrMsgsInternal'); 966 declare_variable("$r.10.4.5", 'ContentBadHdrMsgsOriginating'); 967 declare_variable("$r.10.4.6", 'ContentBadHdrMsgsOpenRelay'); 968 969 declare_variable("$r.10.5.1", 'ContentSpammyMsgs'); 970 declare_variable("$r.10.5.2", 'ContentSpammyMsgsInbound'); 971 declare_variable("$r.10.5.3", 'ContentSpammyMsgsOutbound'); 972 declare_variable("$r.10.5.4", 'ContentSpammyMsgsInternal'); 973 declare_variable("$r.10.5.5", 'ContentSpammyMsgsOriginating'); 974 declare_variable("$r.10.5.6", 'ContentSpammyMsgsOpenRelay'); 975 976 declare_variable("$r.10.6.1", 'ContentSpamMsgs'); 977 declare_variable("$r.10.6.2", 'ContentSpamMsgsInbound'); 978 declare_variable("$r.10.6.3", 'ContentSpamMsgsOutbound'); 979 declare_variable("$r.10.6.4", 'ContentSpamMsgsInternal'); 980 declare_variable("$r.10.6.5", 'ContentSpamMsgsOriginating'); 981 declare_variable("$r.10.6.6", 'ContentSpamMsgsOpenRelay'); 982 983 declare_variable("$r.10.7.1", 'ContentUncheckedMsgs'); 984 declare_variable("$r.10.7.2", 'ContentUncheckedMsgsInbound'); 985 declare_variable("$r.10.7.3", 'ContentUncheckedMsgsOutbound'); 986 declare_variable("$r.10.7.4", 'ContentUncheckedMsgsInternal'); 987 declare_variable("$r.10.7.5", 'ContentUncheckedMsgsOriginating'); 988 declare_variable("$r.10.7.6", 'ContentUncheckedMsgsOpenRelay'); 989 990 declare_variable("$r.10.8.1", 'ContentBannedMsgs'); 991 declare_variable("$r.10.8.2", 'ContentBannedMsgsInbound'); 992 declare_variable("$r.10.8.3", 'ContentBannedMsgsOutbound'); 993 declare_variable("$r.10.8.4", 'ContentBannedMsgsInternal'); 994 declare_variable("$r.10.8.5", 'ContentBannedMsgsOriginating'); 995 declare_variable("$r.10.8.6", 'ContentBannedMsgsOpenRelay'); 996 997 declare_variable("$r.10.9.1", 'ContentVirusMsgs'); 998 declare_variable("$r.10.9.2", 'ContentVirusMsgsInbound'); 999 declare_variable("$r.10.9.3", 'ContentVirusMsgsOutbound'); 1000 declare_variable("$r.10.9.4", 'ContentVirusMsgsInternal'); 1001 declare_variable("$r.10.9.5", 'ContentVirusMsgsOriginating'); 1002 declare_variable("$r.10.9.6", 'ContentVirusMsgsOpenRelay'); 1003 1004 declare_variable("$r.11.1", 'CacheAttempts'); 1005 declare_variable("$r.11.2", 'CacheMisses'); 1006 declare_variable("$r.11.3", 'CacheHits'); 1007 declare_variable("$r.11.4", 'CacheHitsVirusCheck'); 1008 declare_variable("$r.11.5", 'CacheHitsVirusMsgs'); 1009 declare_variable("$r.11.6", 'OutConnNew'); 1010 declare_variable("$r.11.7", 'OutConnQuit'); 1011 declare_variable("$r.11.8", 'OutConnTransact'); 1012 declare_variable("$r.11.9", 'OutConnReuseFail'); 1013 declare_variable("$r.11.10", 'OutConnReuseRecent'); 1014 declare_variable("$r.11.11", 'OutConnReuseRefreshed'); 1015 1016 declare_variable("$r.12.1", 'OpsDec'); 1017 declare_variable("$r.12.2", 'OpsSpamCheck'); 1018 declare_variable("$r.12.3", 'OpsVirusCheck'); 1019 1020 declare_variable("$r.13.1", 'PenPalsAttempts'); 1021 declare_variable("$r.13.2", 'PenPalsAttemptsRid'); 1022 declare_variable("$r.13.3", 'PenPalsAttemptsMid'); 1023 declare_variable("$r.13.4", 'PenPalsMisses'); 1024 declare_variable("$r.13.5", 'PenPalsHits'); 1025 declare_variable("$r.13.6", 'PenPalsHitsRid'); 1026 declare_variable("$r.13.7", 'PenPalsHitsMid'); 1027 declare_variable("$r.13.8", 'PenPalsHitsMidRid'); 1028 declare_variable("$r.13.9", 'PenPalsSavedFromTag2'); 1029 declare_variable("$r.13.10", 'PenPalsSavedFromTag3'); 1030 declare_variable("$r.13.11", 'PenPalsSavedFromKill'); 1031 1032 declare_variable("$r.14.1", 'SqlAddrSenderAttempts'); 1033 declare_variable("$r.14.2", 'SqlAddrSenderMisses'); 1034 declare_variable("$r.14.3", 'SqlAddrSenderHits'); 1035 declare_variable("$r.14.4", 'SqlAddrRecipAttempts'); 1036 declare_variable("$r.14.5", 'SqlAddrRecipMisses'); 1037 declare_variable("$r.14.6", 'SqlAddrRecipHits'); 1038 1039 declare_variable("$r.15.1", 'LogEntries', 'C64'); 1040 declare_variable("$r.15.2", 'LogEntriesEmerg', 'C64'); 1041 declare_variable("$r.15.3", 'LogEntriesAlert', 'C64'); 1042 declare_variable("$r.15.4", 'LogEntriesCrit', 'C64'); # lvl le -3 1043 declare_variable("$r.15.5", 'LogEntriesErr', 'C64'); # lvl le -2 1044 declare_variable("$r.15.6", 'LogEntriesWarning', 'C64'); # lvl le -1 1045 declare_variable("$r.15.7", 'LogEntriesNotice', 'C64'); # lvl le 0 1046 declare_variable("$r.15.8", 'LogEntriesInfo', 'C64'); # lvl le 1 1047 declare_variable("$r.15.9", 'LogEntriesDebug', 'C64'); # lvl le 2 1048 declare_variable("$r.15.10", 'LogEntriesLevel0', 'C64'); # le 0 1049 declare_variable("$r.15.11", 'LogEntriesLevel1', 'C64'); # eq 1 1050 declare_variable("$r.15.12", 'LogEntriesLevel2', 'C64'); # eq 2 1051 declare_variable("$r.15.13", 'LogEntriesLevel3', 'C64'); # eq 3 1052 declare_variable("$r.15.14", 'LogEntriesLevel4', 'C64'); # eq 4 1053 declare_variable("$r.15.15", 'LogEntriesLevel5', 'C64'); # ge 5 1054 declare_variable("$r.15.16", 'LogLines', 'C64'); 1055 declare_variable("$r.15.17", 'LogRetries', 'C64'); 1056 1057 declare_variable("$r.16.1", 'TimeElapsedTotal', 'INT'); 1058 declare_variable("$r.16.2", 'TimeElapsedReceiving', 'INT'); 1059 declare_variable("$r.16.3", 'TimeElapsedSending', 'INT'); 1060 declare_variable("$r.16.4", 'TimeElapsedDecoding', 'INT'); 1061 declare_variable("$r.16.5", 'TimeElapsedPenPals', 'INT'); 1062 declare_variable("$r.16.6", 'TimeElapsedVirusCheck','INT'); 1063 declare_variable("$r.16.7", 'TimeElapsedSpamCheck', 'INT'); 1064 1065 declare_variable("$r.17.1", 'UserCounter1', 'C64'); 1066 declare_variable("$r.17.2", 'UserCounter2', 'C64'); 1067 declare_variable("$r.17.3", 'UserCounter3', 'C64'); 1068 declare_variable("$r.17.4", 'UserCounter4', 'C64'); 1069 declare_variable("$r.17.5", 'UserCounter5', 'C64'); 1070 declare_variable("$r.17.6", 'UserCounter6', 'C64'); 1071 declare_variable("$r.17.7", 'UserCounter7', 'C64'); 1072 declare_variable("$r.17.8", 'UserCounter8', 'C64'); 1073 declare_variable("$r.17.9", 'UserCounter9', 'C64'); 1074 declare_variable("$r.17.10", 'UserCounter10', 'C64'); 1075 1076 declare_variable("$r.18.1", 'UserGauge1', 'G32'); 1077 declare_variable("$r.18.2", 'UserGauge2', 'G32'); 1078 declare_variable("$r.18.3", 'UserGauge3', 'G32'); 1079 declare_variable("$r.18.4", 'UserGauge4', 'G32'); 1080 declare_variable("$r.18.5", 'UserGauge5', 'G32'); 1081 declare_variable("$r.18.6", 'UserGauge6', 'G32'); 1082 declare_variable("$r.18.7", 'UserGauge7', 'G32'); 1083 declare_variable("$r.18.8", 'UserGauge8', 'G32'); 1084 declare_variable("$r.18.9", 'UserGauge9', 'G32'); 1085 declare_variable("$r.18.10", 'UserGauge10', 'G32'); 1086 1087 declare_variable("$r.19.1", 'InMsgsStatusAccepted'); # 2xx, AM.PDP 1088 declare_variable("$r.19.2", 'InMsgsStatusAcceptedInbound'); 1089 declare_variable("$r.19.3", 'InMsgsStatusAcceptedOutbound'); 1090 declare_variable("$r.19.4", 'InMsgsStatusAcceptedInternal'); 1091 declare_variable("$r.19.5", 'InMsgsStatusAcceptedOriginating'); 1092 declare_variable("$r.19.6", 'InMsgsStatusAcceptedOpenRelay'); 1093 1094 declare_variable("$r.20.1", 'InMsgsStatusRelayedUntagged'); # 2xx, fwd 1095 declare_variable("$r.20.2", 'InMsgsStatusRelayedUntaggedInbound'); 1096 declare_variable("$r.20.3", 'InMsgsStatusRelayedUntaggedOutbound'); 1097 declare_variable("$r.20.4", 'InMsgsStatusRelayedUntaggedInternal'); 1098 declare_variable("$r.20.5", 'InMsgsStatusRelayedUntaggedOriginating'); 1099 declare_variable("$r.20.6", 'InMsgsStatusRelayedUntaggedOpenRelay'); 1100 1101 declare_variable("$r.21.1", 'InMsgsStatusRelayedTagged'); # 2xx, forward 1102 declare_variable("$r.21.2", 'InMsgsStatusRelayedTaggedInbound'); 1103 declare_variable("$r.21.3", 'InMsgsStatusRelayedTaggedOutbound'); 1104 declare_variable("$r.21.4", 'InMsgsStatusRelayedTaggedInternal'); 1105 declare_variable("$r.21.5", 'InMsgsStatusRelayedTaggedOriginating'); 1106 declare_variable("$r.21.6", 'InMsgsStatusRelayedTaggedOpenRelay'); 1107 1108 declare_variable("$r.22.1", 'InMsgsStatusDiscarded'); # 2xx, no DSN 1109 declare_variable("$r.22.2", 'InMsgsStatusDiscardedInbound'); 1110 declare_variable("$r.22.3", 'InMsgsStatusDiscardedOutbound'); 1111 declare_variable("$r.22.4", 'InMsgsStatusDiscardedInternal'); 1112 declare_variable("$r.22.5", 'InMsgsStatusDiscardedOriginating'); 1113 declare_variable("$r.22.6", 'InMsgsStatusDiscardedOpenRelay'); 1114 1115 declare_variable("$r.23.1", 'InMsgsStatusNoBounce'); # 2xx, no DSN 1116 declare_variable("$r.23.2", 'InMsgsStatusNoBounceInbound'); 1117 declare_variable("$r.23.3", 'InMsgsStatusNoBounceOutbound'); 1118 declare_variable("$r.23.4", 'InMsgsStatusNoBounceInternal'); 1119 declare_variable("$r.23.5", 'InMsgsStatusNoBounceOriginating'); 1120 declare_variable("$r.23.6", 'InMsgsStatusNoBounceOpenRelay'); 1121 1122 declare_variable("$r.24.1", 'InMsgsStatusBounced'); # 2xx, DSN sent 1123 declare_variable("$r.24.2", 'InMsgsStatusBouncedInbound'); 1124 declare_variable("$r.24.3", 'InMsgsStatusBouncedOutbound'); 1125 declare_variable("$r.24.4", 'InMsgsStatusBouncedInternal'); 1126 declare_variable("$r.24.5", 'InMsgsStatusBouncedOriginating'); 1127 declare_variable("$r.24.6", 'InMsgsStatusBouncedOpenRelay'); 1128 1129 declare_variable("$r.25.1", 'InMsgsStatusRejected'); # 5xx 1130 declare_variable("$r.25.2", 'InMsgsStatusRejectedInbound'); 1131 declare_variable("$r.25.3", 'InMsgsStatusRejectedOutbound'); 1132 declare_variable("$r.25.4", 'InMsgsStatusRejectedInternal'); 1133 declare_variable("$r.25.5", 'InMsgsStatusRejectedOriginating'); 1134 declare_variable("$r.25.6", 'InMsgsStatusRejectedOpenRelay'); 1135 1136 declare_variable("$r.26.1", 'InMsgsStatusTempFailed'); # 4xx 1137 declare_variable("$r.26.2", 'InMsgsStatusTempFailedInbound'); 1138 declare_variable("$r.26.3", 'InMsgsStatusTempFailedOutbound'); 1139 declare_variable("$r.26.4", 'InMsgsStatusTempFailedInternal'); 1140 declare_variable("$r.26.5", 'InMsgsStatusTempFailedOriginating'); 1141 declare_variable("$r.26.6", 'InMsgsStatusTempFailedOpenRelay'); 1142 } 1143 1144 { # amavisd child processes MIB 1145 my($r) = $databases[1]->{root_oid_str}; 1146 declare_variable("$r.1.1", 'ProcGone'); # counter! 1147 declare_variable("$r.1.2", 'ProcAll', 'G32'); 1148 declare_variable("$r.1.3", 'ProcIdle', 'G32'); 1149 declare_variable("$r.1.4", 'ProcBusy', 'G32'); 1150 declare_variable("$r.1.5", 'ProcBusyTransfer', 'G32'); 1151 declare_variable("$r.1.6", 'ProcBusyDecode', 'G32'); 1152 declare_variable("$r.1.7", 'ProcBusyVirus', 'G32'); 1153 declare_variable("$r.1.8", 'ProcBusySpam', 'G32'); 1154 declare_variable("$r.1.9", 'ProcBusyOther', 'G32'); 1155 declare_variable(sprintf("%s.2.%d", $r,$_+1), 1156 'ProcBusy'.$_, 'G32') for (0..@age_slots); 1157 } 1158 1159 if (defined $mta_queue_dir) { 1160 # Postfix queue size MIB 1161 declare_variable($databases[2]->{root_oid_str}, 1162 'MtaQueueEntriesMaildrop', 'G32'); 1163 declare_variable($databases[3]->{root_oid_str}, 1164 'MtaQueueEntriesIncoming', 'G32'); 1165 declare_variable($databases[4]->{root_oid_str}, 1166 'MtaQueueEntriesActive', 'G32'); 1167 declare_variable($databases[5]->{root_oid_str}, 1168 'MtaQueueEntriesDeferred', 'G32'); 1169 } 1170 1171 if (!$daemonize) { 1172 do_log(0,"%s starting in foreground, perl %s", $myversion,$]); 1173 } else { # daemonize 1174 $SIG{'__WARN__'} = # log warnings 1175 sub { my($m) = @_; chomp($m); do_log(-1,"_WARN: %s",$m) }; 1176 $SIG{'__DIE__' } = # log uncaught errors 1177 sub { if (!$^S) { my($m) = @_; chomp($m); do_log(-2,"_DIE: %s",$m) } }; 1178 openlog($syslog_ident, LOG_PID | LOG_NDELAY, $syslog_facility); 1179 $syslog_open = 1; 1180 do_log(2,"to be daemonized"); 1181 daemonize(); 1182 do_log(0,"%s starting. daemonized as PID [%s], perl %s", $myversion,$$,$]); 1183 if (defined $pid_filename && $pid_filename ne '') { 1184 my($pidf) = IO::File->new; 1185 my($stat) = $pidf->open($pid_filename, O_CREAT|O_EXCL|O_RDWR, 0640); 1186 if (!$stat && $! == EEXIST) { 1187 do_log(0,"PID file %s exists, overwriting", $pid_filename); 1188 $stat = $pidf->open($pid_filename, O_CREAT|O_RDWR, 0640); 1189 } 1190 $stat or die "Can't create file $pid_filename: $!"; 1191 $pid_file_created = 1; 1192 $pidf->print("$$\n") or die "Can't write to $pid_filename: $!"; 1193 $pidf->close or die "Can't close $pid_filename: $!"; 1194 } 1195 } 1196 1197 #netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, 1198 # NETSNMP_DS_LIB_DONT_READ_CONFIGS, 1); 1199 1200 my($agent) = NetSNMP::agent->new('Name' => $agent_name, 'AgentX' => 1) 1201 or die "Can't create a SNMP agent $agent_name"; 1202 1203 init_data(); # must come *after* NetSNMP::agent->new 1204# dump_variables(); 1205 1206 for my $database (@databases) { 1207 my($root_oid_str) = $database->{root_oid_str}; 1208 my($db_name) = $database->{name}; 1209 if ($db_name =~ /^pf/ && !defined $mta_queue_dir) { 1210 do_log(2, "not registering root OID %s for %s", $root_oid_str,$db_name); 1211 } else { 1212 do_log(2, "registering root OID %s for %s", $root_oid_str,$db_name); 1213 $root_oid_str = '.' . $root_oid_str; 1214 $agent->register($agent_name, $root_oid_str, \&snmp_handler) 1215 or die "Can't register a SNMP agent $agent_name under $root_oid_str"; 1216 $database->{registered} = 1; 1217 } 1218 } 1219 1220 while ($keep_running) { 1221 $agent->agent_check_and_process(1); 1222 } 1223 exit; 1224 1225END { 1226 if (defined $agent) { 1227 eval { $agent->shutdown }; # ignoring status 1228 } 1229 for my $database (@databases) { 1230 eval { 1231 if (defined $database->{db}) { 1232 if (defined $database->{cursor}) { 1233 $database->{cursor}->c_close; # close database, ignoring status 1234 undef $database->{cursor}; 1235 } 1236 $database->{db}->db_close == 0 1237 or warn(sprintf("BDB db_close error on a %s file: %s %s", 1238 $database->{db}, $BerkeleyDB::Error, $!)); 1239 } 1240 }; # ignoring status 1241 } 1242 if ($pid_file_created) { 1243 unlink($pid_filename) 1244 or eval { do_log(0, "Can't remove file %s: %s", $pid_filename,$!) }; 1245 } 1246 eval { do_log(2, "%s shutting down", $myproduct_name) }; 1247 if ($syslog_open) { 1248 eval { closelog() }; $syslog_open = 0; 1249 } 1250} 1251