OSSP CVS Repository

ossp - ossp-pkg/snmpdx/snmpdx 1.2
Not logged in
[Honeypot]  [Browse]  [Directory]  [Home]  [Login
[Reports]  [Search]  [Ticket]  [Timeline
  [Raw

ossp-pkg/snmpdx/snmpdx 1.2
#!/bin/sh -- # -*- perl -*-
eval 'exec perl -S $0 ${1+"$@"}'
    if $running_under_some_shell;
##
##  OSSP snmpdx -- SNMP Daemon Extension
##  Copyright (c) 2003 The OSSP Project <http://www.ossp.org/>
##  Copyright (c) 2003 Ralf S. Engelschall <rse@engelschall.com>
##  Copyright (c) 2003 Cable & Wireless <http://www.cw.com/>
##
##  Permission to use, copy, modify, and distribute this software for
##  any purpose with or without fee is hereby granted, provided that
##  the above copyright notice and this permission notice appear in all
##  copies.
##
##  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
##  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
##  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
##  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
##  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
##  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
##  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
##  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
##  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
##  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
##  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
##  SUCH DAMAGE.
##

#   requirements
require 5;
use strict;
use warnings;
use FindBin;
use Getopt::Long;
use IO;

#   program information
my $progname = "snmpdx";
my $progvers = "0.1.0";

##  _________________________________________________________________________
##
##  HELPER CLASS: LOGFILE WRITING
##  _________________________________________________________________________
##

package My::Log;

require Exporter;

our @ISA = qw(Exporter);
our @EXPORT_OK = qw(new destroy DESTROY write);

sub new ($$) {
    my $proto = shift @_;
    my $class = ref $proto || $proto;
    my $self = {};
    bless ($self, $class);
    my $level    = shift @_ || die "missing level argument";
    my $filename = shift @_ || die "missing filename argument";
    $self->{-level} = $level;
    $self->{-io} = new IO::File ">>$filename"
        or die "cannot open logfile \"$filename\" for writing";
    $self->{-io}->autoflush(1);
    return $self;
}

sub destroy ($) {
    my ($self) = @_;
    $self->{-io}->close() if (defined($self->{-io}));
    return;
}

sub DESTROY ($) {
    my ($self) = @_;
    $self->destroy;
    return;
}

sub printf ($;@) {
    my $self  = shift @_;
    my $level = shift @_ || die "missing level argument";
    my $fmt   = shift @_ || die "missing format argument";
    if ($self->{-level} >= $level) {
        my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time());
        my $id = sprintf("[%04d-%02d-%02d/%02d:%02d:%02d@%05d] ", 
                         $year+1900, $mon+1, $mday, $hour, $min, $sec, $$);
        $self->{-io}->printf($id . $fmt . "\n", @_);
    }
    return;
}

##  _________________________________________________________________________
##
##  HELPER CLASS: VALUE CACHING
##  _________________________________________________________________________
##

package My::Cache;

require Exporter;

our @ISA = qw(Exporter);
our @EXPORT_OK = qw(new destroy DESTROY store fetch);

sub new ($) {
    my ($proto) = @_;
    my $class = ref $proto || $proto;
    my $self = {};
    bless ($self, $class);
    $self->{-cache} = {};
    return $self;
}

sub destroy ($) {
    my ($self) = @_;
    return;
}

sub DESTROY ($) {
    my ($self) = @_;
    $self->destroy;
    return;
}

sub store ($$$$) {
    my ($self, $id, $expires, $value) = @_;

    die "invalid id" if (not defined($id) or $id eq '');

    if    ($expires =~ m|^(\d+)[sS]?$|) { $expires = $1; }
    elsif ($expires =~ m|^(\d+)[mM]$|)  { $expires = $1 * 60; }
    elsif ($expires =~ m|^(\d+)[hH]$|)  { $expires = $1 * 60 * 60; }
    elsif ($expires =~ m|^(\d+)[dD]$|)  { $expires = $1 * 60 * 60 * 24; }
    elsif ($expires =~ m|^(\d+)[wW]$|)  { $expires = $1 * 60 * 60 * 24 * 7; }
    elsif ($expires =~ m|^forever$|i)   { $expires = 99999 * 365 * 24 * 60 * 60; }
    else { die "invalid expire time"; }

    $expires = time() + $expires;

    $self->{-cache}->{$id} = { -expires => $expires, -value => $value };

    return;
}

sub fetch ($$) {
    my ($self, $id) = @_;

    my $value = undef;
    if (defined($self->{-cache}->{$id})) {
        if ($self->{-cache}->{$id}->{-expires} > time()) {
            $value = $self->{-cache}->{$id}->{-value};
        }
        else {
            undef $self->{-cache}->{$id};
        }
    }
    return $value;
}

##  _________________________________________________________________________
##
##  HELPER CLASS: RUNNING SYSTEM COMMANDS
##  _________________________________________________________________________
##

package My::System;

require Exporter;

our @ISA = qw(Exporter);
our @EXPORT_OK = qw(new destroy DESTROY run);

sub new ($;$) {
    my ($proto, $log, $cache) = @_;
    my $class = ref $proto || $proto;
    my $self = {};
    bless ($self, $class);
    $self->{-log}   = $log;
    $self->{-cache} = (defined($cache) ? $cache : new My::Cache);
    return $self;
}

sub destroy ($) {
    my ($self) = @_;
    $self->{-cache}->destroy() if (defined($self->{-cache}));
    return;
}

sub DESTROY ($) {
    my ($self) = @_;
    $self->destroy;
    return;
}

sub run ($$;$) {
    my ($self, $cmd, $expires) = @_;

    $expires = "1m" if (not defined($expires));
    my $result = $self->{-cache}->fetch($cmd);
    if (defined($result)) {
        $self->{-log}->printf(4, "system: run: \"$cmd\" ($expires) [CACHE HIT]");
    }
    else {
        $self->{-log}->printf(3, "system: run: \"$cmd\" ($expires) [CACHE MISS]");
        $self->{-log}->printf(5, "system: executing command: \"$cmd\""); 
        $result = { -stdout => '', -rv => 0 };
        $result->{-stdout} = `$cmd 2>/dev/null`;
        $result->{-rv} = ($? >> 8);
        $self->{-log}->printf(6, "system: return value: ". $result->{-rv}); 
        $self->{-cache}->store($cmd, $expires, $result);
    }
    return $result;
}

##  _________________________________________________________________________
##
##  HELPER CLASS: PLATFORM IDENTIFICATION
##  _________________________________________________________________________
##

package My::Platform;

require Exporter;

our @ISA = qw(Exporter);
our @EXPORT_OK = qw(new destroy DESTROY id);

sub new ($) {
    my ($proto) = @_;
    my $class = ref $proto || $proto;
    my $self = {};
    bless ($self, $class);
    $self->{-machine} = `(uname -m) 2>/dev/null`;
    $self->{-machine} = `(uname -p) 2>/dev/null` if ($self->{-machine} eq '');
    $self->{-machine} =~ s|^\s*(.+?)\s*$|$1|s;
    $self->{-system}  = `(uname -s) 2>/dev/null`;
    $self->{-system}  =~ s|^\s*(.+?)\s*$|$1|s;
    $self->{-release} = `(uname -r) 2>/dev/null`;
    $self->{-release} = `(uname -v) 2>/dev/null` if ($self->{-release} eq '');
    $self->{-release} =~ s|^\s*(.+?)\s*$|$1|s;
    return $self;
}

sub destroy ($) {
    my ($self) = @_;
    return;
}

sub DESTROY ($) {
    my ($self) = @_;
    $self->destroy;
    return;
}

sub id ($) {
    my ($self) = @_;
    return sprintf("%s-%s-%s", 
        $self->{-machine}, $self->{-system}, $self->{-release});
}

##  _________________________________________________________________________
##
##  HELPER CLASS: MIB OID/NAME MAPPING
##  _________________________________________________________________________
##

package My::MIB;

require Exporter;

our @ISA = qw(Exporter);
our @EXPORT_OK = qw(new destroy DESTROY oid2name name2oid oid2type);

sub new ($$$) {
    my ($proto, $bindir, $mibdir, $mib) = @_;
    my $class = ref $proto || $proto;
    my $self = {};
    bless ($self, $class);
    $self->{-oid2name} = {};
    $self->{-name2oid} = {};
    my @entries = `$bindir/snmptranslate -M $mibdir -m $mib -Tl`;
    foreach my $entry (@entries) {
        $entry =~ s|\n$||s;
        my $name = $entry;
        my $oid = '';
        $name =~ s|\((\d+)\)|$oid .= ".$1", ''|sge;
        my $out = `$bindir/snmptranslate -M $mibdir -m $mib -Td $oid`;
        if ($out =~ m|\s+MAX-ACCESS\s+not-accessible\s*|si) {
            next;
        }
        my $type;
        if ($out =~ m|TEXTUAL\s+CONVENTION\s+(.+?)\n\s+SYNTAX|si) {
            $type = $1;
        }
        elsif ($out =~ m|SYNTAX\s+(.+?)\s*\n|si) {
            $type = $1;
            $type =~ s|\s+\([^\)]+\)$||s;
        }
        else {
            next;
        }
        if ($name =~ m|[^.]+TABLE\.[^.]+ENTRY\.[^.]+$|) {
            $name .= ".#";
            $oid  .= ".#";
        }
        $self->{-oid2type}->{$oid} = $type;
        $self->{-oid2name}->{$oid} = $name;
        $self->{-name2oid}->{$name} = $oid;
    }
    return $self;
}

sub destroy ($) {
    my ($self) = @_;
    return;
}

sub DESTROY ($) {
    my ($self) = @_;
    $self->destroy;
    return;
}

sub oid2name ($$) {
    my ($self, $oid) = @_;
    return $self->{-oid2name}->{$oid};
}

sub name2oid ($$) {
    my ($self, $name) = @_;
    return $self->{-name2oid}->{$name};
}

sub oid2type ($$) {
    my ($self, $oid) = @_;
    return $self->{-oid2type}->{$oid};
}

sub oids ($;@) {
    my ($self, @patterns) = @_;
    my $oids = [];
    foreach my $pattern (@patterns) {
        $pattern = "*" if (not defined($pattern));
        $pattern =~ s/\./\\./sg;
        $pattern =~ s/\*/.*/sg;
        foreach my $name (keys(%{$self->{-name2oid}})) {
            if ($name =~ m|^${pattern}$|si) {
                push(@{$oids}, $self->{-name2oid}->{$name});
            }
        }
    }
    return $oids;
}

##  _________________________________________________________________________
##
##  HELPER CLASS: PROBE (abstract only)
##  _________________________________________________________________________
##

package My::Probe;

require Exporter;

our @ISA = qw(Exporter);
our @EXPORT_OK = qw(new destroy DESTROY oids probe);

sub new ($$) {
    my ($proto, $ctx) = @_;
    my $class = ref $proto || $proto;
    my $self = {};
    bless ($self, $class);
    $self->{-ctx} = $ctx;
    return $self;
}

sub destroy ($) {
    my ($self) = @_;
    return;
}

sub DESTROY ($) {
    my ($self) = @_;
    $self->destroy;
    return;
}

sub oids ($) {
    my ($self) = @_;
    return [];
}

sub probe ($$$) {
    my ($self, $ctx, $oid) = @_;
    return {};
}

##  _________________________________________________________________________
##
##  HELPER CLASS: Encoding
##  _________________________________________________________________________
##

package My::Enc;

require Exporter;

our @ISA = qw(Exporter);
our @EXPORT_OK = qw(new destroy DESTROY dat_encode dat_decode octet_encode octet_decode);

sub new ($$) {
    my ($proto) = @_;
    my $class = ref $proto || $proto;
    my $self = {};
    bless ($self, $class);
    return $self;
}

sub destroy ($) {
    my ($self) = @_;
    return;
}

sub DESTROY ($) {
    my ($self) = @_;
    $self->destroy;
    return;
}

#   ASN.1 DateAndTime encoding/decoding
sub dat_encode ($$) {
    my ($self, $asc) = @_;
    my ($y, $m, $d, $H, $M, $S) = (0, 0, 0, 0, 0, 0);
    if ($asc =~ m|^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$|) {
        ($y, $m, $d, $H, $M, $S) = ($1, $2, $3, $4, $5, $6);
    }
    return pack("nCCCCCCCCC", $y, $m, $d, $H, $M, $S, 0, 0, 0, 0);
}
sub dat_decode ($$) {
    my ($self, $bin) = @_;
    my ($y, $m, $d, $H, $M, $S) = unpack("nCCCCCCCCC", $bin);
    return sprintf("%04d-%02d-%02d %02d:%02d:%02d", $y, $m, $d, $H, $M, $S);
}

#   snmpd(8) pass_persist protocol 'octet' encoding/decoding
sub octet_encode ($$) {
    my ($self, $bin) = @_;
    my $asc = '';
    $bin =~ s|(.)|$asc .= sprintf("%02x ", ord($1)), ''|sge;
    return $asc;
}
sub octet_decode ($$) {
    my ($self, $asc) = @_;
    my $bin = '';
    $asc =~ s|([0-9a-zA-Z]{2})\s*|$bin .= chr(hex($1)), ''|sge;
    return $bin;
}

##  _________________________________________________________________________
##
##  MAIN PROCEDURE
##  _________________________________________________________________________
##

package main;

#   find path to ourself
my $myroot    = "$FindBin::Bin";

#   parameters (defaults)
my $config    = "$myroot/snmpdx.cfg";
my $version   = 0;
my $help      = 0;
my $tmpdir    = ($ENV{TMPDIR} || $ENV{TEMPDIR} || "/tmp");
my $get       = 0;
my $next      = 0;
my $bindir    = "/cw/bin";
my $probedir  = "$myroot/snmpdx.d";
my $probename = "*";
my $mibdir    = "$myroot/snmpdx.mib";
my $mibname   = "snmpdx";
my $logfile   = "$myroot/snmpdx.log";
my $loglevel  = 9;

#   command line parsing
my %options = (
    'c|config=s'    => \$config,
    'V|version'     => \$version,
    'h|help'        => \$help,
    't|tmpdir=s'    => \$tmpdir,
    'g|get'         => \$get,
    'n|next'        => \$next,
    'b|bindir=s'    => \$bindir,
    'P|probedir=s'  => \$probedir,
    'p|probename=s' => \$probename,
    'M|mibdir=s'    => \$mibdir,
    'm|mibname=s'   => \$mibname,
    'l|logfile=s'   => \$logfile,
    'L|loglevel=i'  => \$loglevel,
);
Getopt::Long::Configure("bundling");
my $result = GetOptions(%options) || die "option parsing failed";
if ($help) {
    print "Usage: $progname [options] [SPECFILE ...]\n" .
          "Available options:\n" .
          " -c,--config=PATH    read command line options from configuration fil\n" .
          " -V,--version        print program version\n" .
          " -h,--help           print out this usage page\n" .
          " -t,--tmpdir=PATH    filesystem path to temporary directory\n" .
          " -g,--get=MIBOID     get value of this MIB OID\n" .
          " -n,--next=MIBOID    get value of next MIB OID\n" .
          " -b,--bindir=PATH    path to the net-snmp binaries\n" .
          " -P,--probedir=PATH  path to probe directory\n" .
          " -p,--probename=NAME the pattern for probes to load\n" .
          " -M,--mibdir=PATH    path to MIB directory\n" .
          " -m,--mibname=MIB    the name of the MIB to act under\n" .
          " -l,--logfile=PATH   path to daemon logfile\n" .
          " -L,--loglevel=NUM   logging level (0...9)\n";
    exit(0);
}
if ($version) {
    print "$progname $progvers\n";
    exit(0);
}

#   support external configuration file
if (-f $config) {
    my $cfg = new IO::File "<$config";
    my $line;
    while (defined($line = $cfg->getline())) {
        $line =~ s|\r?\n$||s;
        next if ($line =~ m/^\s*(#.*)?$/s);
        my ($option, $value) = ($line =~ m|^\s*(\S+)\s+(.+?)\s*$|s);
        die "invalid configuration line \"$line\""
            if (not defined($option) or not defined($value));
        my ($var) = map  { $options{$_} } 
                    grep { $_ =~ m/^.\|\Q$option\E(=[si])?$/s }
                    keys(%options);
        die "invalid configuration option \"$option\""
            if (not defined($var));
        ${$var} = $value;
    }
    $cfg->close;
}

#   create daemon run-time context
my $ctx = {};
$ctx->{-log}      = new My::Log ($loglevel, $logfile);
$ctx->{-cache}    = new My::Cache;
$ctx->{-sys}      = new My::System ($ctx->{-log}, $ctx->{-cache});
$ctx->{-platform} = new My::Platform;
$ctx->{-log}->printf(1, "startup %s %s (%s)", $progname, $progvers, $ctx->{-platform}->id());
$ctx->{-mib}      = new My::MIB ($bindir, $mibdir, $mibname);
$ctx->{-enc}      = new My::Enc;

#   exception handling support 
$SIG{__DIE__} = sub {
    my ($err) = @_;
    $err =~ s|\r?\n$||s;
    $ctx->{-log}->printf(1, "ERROR: $err ". ($! ? "($!)" : ""));
    undef $ctx;
    exit(1);
};
$SIG{__WARN__} = sub {
    my ($err) = @_;
    $err =~ s|\r?\n$||s;
    $ctx->{-log}->printf(1, "WARNING: $err");
    return;
};

#   PROTOCOL   MIB        MIB TYPE            net-snmp API   CONVERT TYPE
#   ---------- ---------- ------------------- -------------- ------- ------
#   string     SNMPv2-TC  DisplayString       ASN_OCTET_STR  -       char *
#   integer    ASN.1      Integer32           ASN_INTEGER    strtol  long
#   unsigned   SNMPv2-SMI Unsigned32          ASN_UNSIGNED   strtoul long
#   counter    SNMPv2-SMI Counter32           ASN_COUNTER    strtoul long
#   octet      ASN.1      OCTET STRING        ASN_OCTET_STR  asc2bin char *
#   opaque     SNMPv2-SMI Opaque              ASN_OPAQUE     asc2bin char *
#   gauge      SNMPv2-SMI Gauge32             ASN_GAUGE      strtoul long
#   objectid   ASN.1      OBJECT IDENTIFIER   ASN_OBJECT_ID  -       char *
#   timetick   SNMPv2-SMI TimeTicks           ASN_TIMETICKS  strtoul long
#   ipaddress  SNMPv2-SMI IpAddress           ASN_IPADDRESS  htonl   long
#   octet      SNMPv2-TC  DateAndTime         ASN_OCTET_STR  asc2bin char *

#   load probes and determine their handled OIDs
my $oidtable   = [];
my $oidhandler = {};
my $oidtype    = {
    'DisplayString'      => [ 'string',    'N.A.'    ],
    'Integer32'          => [ 'integer',   '-1'      ],
    'Unsigned32'         => [ 'unsigned',  '0'       ],
    'Counter32'          => [ 'counter',   '-1'      ],
    'OCTET STRING'       => [ 'octet',     'N.A.'    ],
    'Opaque'             => [ 'opaque',    'N.A.'    ],
    'Gauge32'            => [ 'gauge',     '-1'      ],
    'OBJECT IDENTIFIER'  => [ 'objectid',  '.0'      ],
    'TimeTicks'          => [ 'timetick',  '0'       ],
    'IpAddress'          => [ 'ipaddress', '0.0.0.0' ],
    'DateAndTime'        => [ 'octet',     $ctx->{-enc}->dat_encode("1970-01-01 01:02:03") ],
};
$ctx->{-log}->printf(1, "MIB id: %s", $mibname);
foreach my $probefile (glob("$probedir/$probename.pm")) {
    my $probe = $probefile;
    $probe =~ s|^.*/([^/]+)\.pm$|$1|sg;
    my $p;
    eval {
        local $SIG{__DIE__} = 'DEFAULT';
        eval "require \"$probefile\";";
        die if ($@);
        eval "\$p = new My::Probe::$probe (\$ctx);";
        die if ($@);
    };
    if ($@) {
        die "invalid probe \"$probe\"";
    }
    $ctx->{-log}->printf(2, "Probe: \"%s\"", $probe);
    foreach my $oid (sort(@{$p->oids()})) {
        push(@{$oidtable}, $oid);
        $oidhandler->{$oid} = $p;
        $ctx->{-log}->printf(1, "MIB OID: %s [%s] ::= %s (%s)",
            $oid, $ctx->{-mib}->oid2name($oid),
            $ctx->{-mib}->oid2type($oid),
            $oidtype->{$ctx->{-mib}->oid2type($oid)}->[0]);
    }
}
@{$oidtable} = sort(@{$oidtable});

#   connect I/O channels
my $stdin = new IO::Handle;;
$stdin->fdopen(fileno(STDIN), "r");
$stdin->blocking(1);
my $stdout = new IO::Handle;;
$stdout->fdopen(fileno(STDOUT), "w");
$stdout->autoflush(1);
open(STDERR, ">/dev/null");

#   daemon loop
while (1) {
    #   read next command from snmpd
    my $cmd = $stdin->getline;
    if (not defined($cmd)) {
        $ctx->{-log}->printf(4, "IO: EOF");
        last;
    }
    $cmd =~ s|\n?$||s;
    $ctx->{-log}->printf(4, "IO: recv: << \"%s\\n\"", $cmd);

    if ($cmd =~ m/^PING$/i) {
        #   the PING/PONG protocol part
        $ctx->{-log}->printf(4, "IO: send: >> \"PONG\\n\"");
        $stdout->printf("PONG\n");
    }
    elsif ($cmd =~ m/^GET$/i or $cmd =~ m/^GETNEXT$/i) {
        #   the GET/GETNEXT protocol part
        my $error = 1;
        my $oid = $stdin->getline;
        if (not defined($oid)) {
            $ctx->{-log}->printf(1, "ERROR: EOF instead of OID");
            goto ERROR;
        }
        $oid =~ s|\n?$||s;
        $ctx->{-log}->printf(4, "IO: recv: << \"%s\\n\"", $oid);
        if ($oid !~ m/^(\.\d+)+$/) {
            $ctx->{-log}->printf(1, "ERROR: invalid query OID \"%s\"", $oid);
            goto ERROR;
        }
        my $oid_table = undef;
        if ($cmd =~ m/^GETNEXT$/i) {
            my $oid_parent = $oid;
            $oid_parent =~ s|(\.\d+)$||s;
            for (my $i = 0; defined($oidtable->[$i]); $i++) {
                if ($oidtable->[$i] =~ m|^\Q$oid\E(\.\d+)+$|) {
                    #   OID start with requested OID prefix, so use this OID
                    $oid = $oidtable->[$i];
                    last;
                }
                elsif ($oidtable->[$i] eq $oid) {
                    #   OID is the requested OID exactly, so use next OID
                    if (defined($oidtable->[$i+1])) {
                        $oid = $oidtable->[$i+1];
                        last;
                    }
                    else {
                        goto ERROR; # end-of-MIB case
                    }
                }
                elsif ($oidtable->[$i] =~ m|^\Q$oid\E(\.\d+)*(\.#)$|) {
                    #   OID start with requested OID prefix, so use this OID (special case of table)
                    $oid = $oidtable->[$i];
                    $oid =~ s|\.#$|.1|s;
                    last;
                }
                elsif ($oidtable->[$i] =~ m|^\Q$oid_parent\E\.#$|) {
                    #   OID is the a requested OID of a table, so use next OID in table
                    $oid = s|\.(\d+)$|".".($1+1)|se;
                    $oid_table = $oidtable->[$i+1];
                    $oid =~ s|\.#$|.1|s;
                    last;
                }
            }
        }
        TABLE_NEXT:
        my $oid_wild = $oid;
        my $handler = $oidhandler->{$oid};
        if (not defined($handler)) {
            $oid_wild =~ s|(\.\d+)$|.#|s;
            $handler = $oidhandler->{$oid_wild};
        }
        if (not defined($handler)) {
            $ctx->{-log}->printf(1, "ERROR: no handler found for OID \"%s\"", $oid);
            goto ERROR;
        }
        my $obj = {
            -oid   => $oid,
            -name  => $ctx->{-mib}->oid2name($oid_wild),
            -type  => $ctx->{-mib}->oid2type($oid_wild),
            -value => undef
        };
        $handler->probe($obj);
        if (not defined($obj->{-value})) {
            if (defined($oid_table)) {
                #   end of table entry
                $oid = $oid_table;
                goto TABLE_NEXT;
            }
            #$ctx->{-log}->printf(1, "ERROR: handler was unable to provide probe value for OID \"%s\"", $oid);
            #goto ERROR;
            $ctx->{-log}->printf(1, "WARNING: handler was unable to provide probe value for OID \"%s\"", $oid);
            $obj->{-value} = $oidtype->{$obj->{-type}}->[1];
        }
        if ($oidtype->{$obj->{-type}}->[0] =~ m/^(octet|opaque)$/) {
            $obj->{-value} = $ctx->{-enc}->octet_encode($obj->{-value});
        }
        $ctx->{-log}->printf(1, "QUERY: %s [%s] ::= %s \"%s\"",
            $obj->{-name}, $obj->{-oid}, $obj->{-type}, $obj->{-value});
        $stdout->printf("%s\n%s\n%s\n",
            $obj->{-oid}, $oidtype->{$obj->{-type}}->[0], $obj->{-value});
        $ctx->{-log}->printf(4, "IO: send: >> \"%s\\n%s\\n%s\\n\"",
            $obj->{-oid}, $oidtype->{$obj->{-type}}->[0], $obj->{-value});
        $error = 0;

        if ($error) {
            #   end-of-MIB or error
            ERROR:
            $ctx->{-log}->printf(4, "IO: send: >> \"NONE\\n\"");
            $stdout->printf("NONE\n");
        }
    }
    else {
        #   for anything else (not expected) just send 
        #   something in case snmpd expects something.
        $ctx->{-log}->printf(4, "IO: send: >> \"\"");
        $stdout->printf("\n");
    }
}

#   shutdown gracefully
$ctx->{-log}->printf(1, "shutdown");
undef $ctx;
exit(0);


CVSTrac 2.0.1