From e30ddffa198dedeed50c1d9e2bbb98cabbec2eae Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Wed, 22 May 2019 15:35:50 +0300 Subject: New utility: netsnmp-sendmail-setup --- Makefile.PL | 3 +- netsnmp-sendmail-setup | 470 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 472 insertions(+), 1 deletion(-) create mode 100755 netsnmp-sendmail-setup diff --git a/Makefile.PL b/Makefile.PL index 5645635..e496bc9 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -1,5 +1,5 @@ # SNMP Sendmail Statistics Module -*- perl -*- -# Copyright (C) 2015, 2016 Sergey Poznyakoff +# Copyright (C) 2015, 2016, 2019 Sergey Poznyakoff # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -28,6 +28,7 @@ WriteMakefile( 'PM' => { 'Sendmail.pm' => '$(INST_LIBDIR)/Sendmail.pm' }, + 'EXE_FILES' => [ 'netsnmp-sendmail-setup' ], 'PREREQ_PM' => { 'NetSNMP::OID' => 0, 'NetSNMP::agent' => 0, 'NetSNMP::ASN' => 0 }, diff --git a/netsnmp-sendmail-setup b/netsnmp-sendmail-setup new file mode 100755 index 0000000..86e8002 --- /dev/null +++ b/netsnmp-sendmail-setup @@ -0,0 +1,470 @@ +#!/bin/sh +#! -*-perl-*- +# This file is part of NetSNMP::Sendmail +# Copyright (C) 2019 Sergey Poznyakoff +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +eval 'exec perl -x -wS $0 ${1+"$@"}' + if 0; + +use strict; +use warnings; +use File::Temp; +use File::Basename; +use IPC::Cmd qw(can_run); +use POSIX qw(strftime); +use Fcntl; +use Getopt::Long qw(:config gnu_getopt no_ignore_case require_order); +use Pod::Usage; + +sub addts { + my $fd = shift; + print $fd "# Line added by $0 at " + . strftime("%Y-%m-%dT%H:%M:%S", localtime) + . "\n"; +} + +use constant { + EX_OK => 0, # Success + EX_UNCHANGED => 1, # Files not changed + EX_FATAL => 2, # Fatal error + EX_USAGE => 64 # Usage error +}; + +my $suppress_level = 0; +my $dry_run; +my $changed = 0; +my @updated_services; + +use constant { + L_INFO => 0, + L_NOTICE => 1, + L_WARN => 2, + L_ERR => 3 +}; + +use constant MAX_SUPPRESS_LEVEL => L_NOTICE; + +sub printlog { + my $level = shift; + if ($suppress_level <= $level) { + my $fh = (($level >= L_WARN) ? \*STDERR : \*STDOUT); + print $fh "$0: ".join(' ',@_)."\n"; + } +} + +my @restart_commands = ( + [qw(systemctl restart $service)], + [qw(service $service restart)], + [qw(/etc/init.d/$service restart)], + [qw(/etc/rc.d/$service restart)], +); +my %restart_override; + +sub restart_service { + my $service = shift; + + my $cmd = $restart_override{$service}; + return if $cmd && $cmd eq 'no'; + + printlog(L_NOTICE, "restarting $service"); + return if $dry_run; + + if ($cmd) { + printlog(L_NOTICE, "running $cmd"); + system($cmd); + } else { + foreach $cmd (@restart_commands) { + my @c = map { (my $s = $_) =~ s/\$service/$service/g; $s } @$cmd; + if (can_run($c[0])) { + printlog(L_NOTICE, "running @c"); + system(@c); + return if ($? == 0); + } + } + } +} + +sub scan_snmpd_conf { + my ($file, $fd) = @_; + + my $line = 0; + my $comline; + my $insert_line; + + while (<$fd>) { + ++$line; + chomp; + s/^\s+//; + if (/^perl\s+use\s+NetSNMP::Sendmail/) { + printlog(L_INFO, "$file:$line: NetSNMP::Sendmail already enabled"); + return; + } + if (/^perl\s+use/) { + $insert_line = $line; + } + } + return $insert_line || $line + 1; +} + +sub update_snmpd_conf { + my ($ifile, $ifd, $insert_line, $stmt) = @_; + + seek($ifd, 0, SEEK_SET) or die "seek: $!"; + my $ofd = File::Temp->new(DIR => dirname($ifile), UNLINK => $dry_run); + my $line = 0; + while (<$ifd>) { + chomp; + ++$line; + if ($insert_line && $line == $insert_line) { + printlog(L_NOTICE, "editing $ifile (line $line)"); + addts $ofd; + print $ofd "$stmt\n"; + $insert_line = undef; + } + print $ofd "$_\n"; + } + if ($insert_line) { + printlog(L_NOTICE, "editing $ifile (append)"); + addts $ofd; + print $ofd "$stmt\n"; + } + $changed++; + return $ofd; +} + +sub edit_snmpd_conf { + my ($name, $stmt) = @_; + my $u = umask(077); + if (open(my $fd, '<', $name)) { + if (defined(my $insert_line = scan_snmpd_conf($name, $fd))) { + my $fh = update_snmpd_conf($name, $fd, $insert_line, $stmt); + unless ($dry_run) { + my $bk = "$name~"; + unlink $bk if -e $bk; + rename $name, $bk or die "can't rename $name to $bk: $!"; + unless (rename $fh->filename, $name) { + printlog(L_WARN, + "can't rename ".$fh->filename." to $name: $!; restoring from backup"); + unless (rename $bk, $name) { + printlog(L_ERR, "failed to rename $bk to $name: $!"); + exit(EX_FATAL); + } + } + } + push @updated_services, 'snmpd'; + } + close $fd; + } else { + printlog(L_ERR, "can't open $name: $!"); + exit(EX_FATAL); + } + umask($u); +} + +sub check_file { + my $file = shift; + if (-f $file) { + if (! -r $file) { + printlog(L_ERR, "$file is not readable"); + exit(EX_FATAL); + } + } else { + printlog(L_ERR, "$file does not exist"); + exit(EX_FATAL); + } +} + +# Check if NetSNMP::Sendmail is available. +# To avoid namespace contamination, do it in a subprocess. +# The module requires SmtpAgent.pm, whic spits out lots of messages +# to STDERR when loaded outside of snmpd, so first redirect stderr +# to /dev/null. +sub check_module { + my $pid = fork(); + die "fork failed" unless defined $pid; + if ($pid == 0) { + open(STDERR, '>', '/dev/null'); + require NetSNMP::Sendmail; + exit 0; + } + wait; + if ($?) { + printlog(L_ERR, "NetSNMP::Sendmail doesn't seem to be installed"); + exit(EX_FATAL); + } +} + +sub scan_sendmail_mc { + my $fd = shift; + my $name; + my $last_nl; + while (<$fd>) { + $last_nl = chomp; + s/^\s+//; + if (/^define\(\s*`?STATUS_FILE'?\s*,\s*`?(.+?)'?\s*\)/) { + $name = $1; + } + } + return ($name, $last_nl); +} + +sub edit_sendmail_mc { + my $file = shift; + if (open(my $fd, '+<', $file)) { + my $need_make; + my ($statfile, $last_nl) = scan_sendmail_mc($fd); + if ($statfile) { + printlog(L_INFO, "status file $statfile"); + } else { + $statfile = shift; + printlog(L_NOTICE, "editing $file"); + unless ($dry_run) { + seek($fd, 0, SEEK_END) or die "seek: $!"; + print $fd "\n" unless $last_nl; + addts $fd; + print $fd "define(`STATUS_FILE', `$statfile')\n"; + } + push @updated_services, 'sendmail'; + $need_make = 1; + $changed++; + } + close $fd; + + if (-f $statfile) { + printlog(L_INFO, "$statfile exists"); + } else { + printlog(L_NOTICE, "creating $statfile"); + unless ($dry_run) { + if (open($fd, '>', $statfile)) { + close($fd); + } else { + warn "failed to create $statfile: $!"; + } + } + } + + if ($need_make) { + my $sendmail_dir = dirname($file); + printlog(L_NOTICE, "running make in $sendmail_dir"); + system("make " + . ($dry_run ? '-n ' : '') + . "-C $sendmail_dir"); + } + } else { + printlog(L_ERR, "can't open $file: $!"); + exit(EX_FATAL); + } +} + +# Main +my $snmpd_conf = '/etc/snmp/snmpd.conf'; +my $sendmail_mc = '/etc/mail/sendmail.mc'; +my $sendmail_statfile = '/etc/mail/sendmail.st'; +my $sendmail_bindir; + +GetOptions('quiet|q+' => \$suppress_level, + 'dry-run|n' => \$dry_run, + 'status-file=s' => \$sendmail_statfile, + 'bindir=s' => \$sendmail_bindir, + 'restart=s' => sub { + my ($name,$command) = split /=/, $_[1], 2; + $restart_override{$name} = $command; + }, + 'help' => sub { + pod2usage(-exitstatus => EX_OK, -verbose => 2); + }, + 'usage' => sub { + pod2usage(-exitstatus => EX_OK, -verbose => 0); + } +) or pod2usage(-exitstatus => EX_USAGE, -verbose => 0, -output => \*STDERR); + +pod2usage(-exitstatus => EX_USAGE, -verbose => 0, -output => \*STDERR) + if @ARGV; + +if ($suppress_level > MAX_SUPPRESS_LEVEL) { + $suppress_level = MAX_SUPPRESS_LEVEL; +} + +check_module; +check_file $snmpd_conf; +check_file $sendmail_mc; +check_file '/etc/mail/Makefile'; + +unless ($sendmail_bindir) { + if (-d "/usr/lib/sm.bin") { + $sendmail_bindir = "/usr/lib/sm.bin"; + } +} + +my $stmt = 'perl use NetSNMP::Sendmail'; +if ($sendmail_bindir) { + $stmt .= ' qw(:config bindir /usr/lib/sm.bin)'; +} +$stmt .= ';'; + +edit_sendmail_mc($sendmail_mc, $sendmail_statfile); +edit_snmpd_conf($snmpd_conf, $stmt); + +map { restart_service($_) } @updated_services; + +exit($changed ? EX_OK : EX_UNCHANGED); + +__END__ +=head1 NAME + +netsnmp-sendmail-setup - sets up Sendmail monitoring via SNMP + +=head1 SYNOPSIS + +B +[B<-nq>] +[B<--bindir=I>] +[B<--dry-run>] +[B<--status-file=I>] +[B<--quiet>] +[B<--restart=I=I>] + +B B<--help> | B<--usage> + +=head1 DESCRIPTION + +Sets up B and B for obtaining Sendmail statistics via +SNMP. First, it checks whether the Sendmail configuration source +F contains the B clause and adds it +if not. Then, it creates the status file and runs B in the F +directory. Finally, the file F is scanned for the +B statement. It is added if not already present. + +=head1 OPTIONS + +=over 4 + +=item B<--bindir=I> + +Some installations place Sendmail binaries in a separate directory, which +is not included in the B<$PATH> environment variable. Use this option to +inform B about this. + +Notice for the users of Debian-based systems: the F +directory is picked up automatically. + +=item B<-n>, B<--dry-run> + +Dry run mode. Don't modify any files, just print what would have been done +and exit with the appropriate error code (see B section). + +Use the B<--quiet> option to control the amount of data printed. + +=item B<-q>, B<--quiet> + +Quiet mode. When used once, suppresses informative output. When used twice, +suppresses both informative output and notification messages about modified +files. + +=item B<--restart=I=I> + +Use I to restart system service I (either B or +B). Use B<--restart=I=no> to skip restarting this +particular I. + +In the absence of this option, B uses the first +available command from the following list: + + systemctl restart $service + service $service restart + /etc/init.d/$service restart + /etc/rc.d/$service restart + +=item B<--status-file=I> + +Name of the Sendmail status file to use when generating the +B statement in the Sendmail configuration file. + +=item B<--help> + +Displays short help message. + +=item B<--usage> + +Displaye short usage message. + +=back + +=head1 FILES + +=over 4 + +=item F + +This file is supposed to recreate the B file from B +if no special goal is given. + +=item F + +Default B configuration file. This file must exist. + +=item F + +Default source file for creating B. + +=item F + +Default statistics file to use. Can be changed using the B<--status-file> +option. + +=item F + +Default directory for Sendmail binaries on Debian-based installations. If +exists, it will be used in the B configuration. + +This can be changed using the B<--bindir> command line option. + +=back + +=head1 EXIT STATUS + +=over 4 + +=item B<0> + +Success. + +=item B<1> + +Success, no modification was necessary. + +=item B<2> + +Fatal error occurred. + +=item B<64> + +Command line usage error. + +=back + +=head1 SEE ALSO + +B(3). + +=head1 BUGS + +The Sendmail configuration directory and the name of the B +configuration file are hardcoded. + +The command relies on B to create F from +F. + +=cut -- cgit v1.2.1