"Fossies" - the Fresh Open Source Software Archive

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