"Fossies" - the Fresh Open Source Software Archive

Member "amavisd-new-2.11.1/amavisd-snmp-subagent" (27 Jan 2014, 54172 Bytes) of package /linux/misc/amavisd-new-2.11.1.tar.bz2:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Perl source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 #!/usr/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 
   44 package AmavisAgent;
   45 
   46 use strict;
   47 use re 'taint';
   48 use warnings;
   49 use warnings FATAL => qw(utf8 void);
   50 no warnings 'uninitialized';
   51 
   52 use Errno qw(ESRCH ENOENT EACCES EEXIST);
   53 use POSIX ();
   54 use Time::HiRes ();
   55 use IO::File qw(O_RDONLY O_WRONLY O_RDWR O_CREAT O_EXCL);
   56 use Unix::Syslog qw(:macros :subs);
   57 use BerkeleyDB;
   58 
   59 use vars qw($VERSION);  $VERSION = 1.007;
   60 
   61 use 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)";
   65 my($agent_name) = $myproduct_name;
   66 
   67 use vars qw($syslog_ident $syslog_facility);
   68 $syslog_ident = $myproduct_name;
   69 $syslog_facility = LOG_MAIL;
   70 
   71 my($db_home) =  # DB databases directory
   72   defined $ENV{'AMAVISD_DB_HOME'} ? $ENV{'AMAVISD_DB_HOME'} : '/var/amavis/db';
   73 
   74 my($mta_queue_dir);
   75 
   76 my($top) = '1.3.6.1.4.1.15312.2.1';
   77 my(@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 
   99 my($log_level) = 0;
  100 my($daemonize) = 1;
  101 my($pid_filename);  # e.g. "/var/run/amavisd-snmp-subagent.pid";
  102 
  103 my($pid_file_created) = 0;
  104 my($syslog_open) = 0;
  105 my($num_proc_gone) = 0;
  106 
  107 # geometic progression, rounded,
  108 #   common ratio = exp((ln(60)-ln(1))/6) = 1.97860
  109 my(@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 
  116 package AmavisVariable;
  117 
  118 sub new    { my($class) = @_; bless [(undef) x 7], $class }
  119 sub oid    { my($self)=shift; !@_ ? $self->[0] : ($self->[0]=shift) }
  120 sub oidstr { my($self)=shift; !@_ ? $self->[1] : ($self->[1]=shift) }
  121 sub name   { my($self)=shift; !@_ ? $self->[2] : ($self->[2]=shift) }
  122 sub type   { my($self)=shift; !@_ ? $self->[3] : ($self->[3]=shift) }
  123 sub suffix { my($self)=shift; !@_ ? $self->[4] : ($self->[4]=shift) }
  124 sub value  { my($self)=shift; !@_ ? $self->[5] : ($self->[5]=shift) }
  125 sub next   { my($self)=shift; !@_ ? $self->[6] : ($self->[6]=shift) }
  126 
  127 
  128 package AmavisAgent;
  129 
  130 use NetSNMP::OID;
  131 use NetSNMP::ASN qw(:all);
  132 use NetSNMP::agent qw(:all);
  133 use NetSNMP::default_store qw(:all);
  134 
  135 my(%oidstr_to_obj);
  136 my(@oid_sorted_list);
  137 my($keep_running) = 1;
  138 my(%variables);
  139 
  140 my(%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 
  153 my(%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 
  167 sub 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
  186 sub 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
  193 sub 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)
  200 sub 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 
  210 sub 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 
  238 sub 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 
  262 sub 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 
  274 sub 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 
  293 END
  294   }
  295 }
  296 
  297 sub 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 
  320 sub 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 
  375 sub count_files_in_postfix_dir($$);  # prototype
  376 sub 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 
  401 sub 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 
  556 sub 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 
  567 my($fast_poll) = 0;
  568 my($last_query_timestamp) = 0;
  569 
  570 sub 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 
  676 sub 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 
  731 sub usage() {
  732   return <<"EOD";
  733 Usage:
  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
  744 EOD
  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 
 1225 END {
 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 }