..And fixed.

Here's the updated checkhost script, which now uses IO::Socket::INET
instead of Net::Telnet *and* properly uses the Bacula ping.  It can
optionally do wake-on-lan, but some external setup is required.


-- 
  Phil Stracchino
  Babylon Communications
  ph...@caerllewys.net
  p...@co.ordinate.org
  Landline: +1.603.293.8485
  Mobile:   +1.603.998.6958
#!/usr/bin/perl
#
# checkhost version 2.0 by Phil Stracchino
# All rights assigned to the Bacula project
# Licensed under the GPL v2, or at your option any later version
#
# Check whether a bacula client is alive and responding on the network.
# Optionally, send a wake-on-LAN packet to the client before testing.
#
# Checkhost does not perform a Bacula connection handshake or verify
# that passwords match; it only verifies that the client is reachable
# on the network and responding, and that the Bacula client is running
# and listening on port 9102.
#
# Usage:  checkhost [-r] [-v] [-i seconds] hostname|ipaddress
# Options:
#     -r, --retry:    Try three times to connect (default: try only once)
#     -i, --interval: Specify time in seconds to wait between retries
#                         (default: 30 seconds)
#     -v, --verbose:  output verbose status messages, for interactive
#                     use (default:  operate silently)
#     -w, --wake:     send wake-on-LAN packet before trying to connect
#
# Wake-on-LAN functionality requires the following schema to be created, by
# default, with a correct authentication group set up in .my.cnf for the
# group Bacula runs as:
# CREATE DATABASE ethers;
# CREATE TABLE ethers.ethers (id int primary key auto_increment,
#                             hostname varchar(128),
#                             ethers varchar(128),
#                             index host (hostname));
# The hostname field should contain a short or fully qualified hostname,
# while the ethers field should contain a list of hardware MAC addresses
# for that host, separated by spaces.

use strict;
use Getopt::Long;
use POSIX;
use DBI;
use IO::Socket::INET;
use Net::Ping;


# Return values:
#       -1  Program error or no host specified
#        0  Success, FD found and responding
#        1  Client alive but FD not listening
#        2  Client not found on network

my $ret = -1;
my ($etherdb, $ethergroup) = qw(ethers ethers); # substitute your DB name/group 
here as appropriate,
                                                # IF you are using the 
wake-on-LAN feature
                                                
my $broadcast = '255.255.255.255';              # set this to your network 
broadcast address
my $my_ip = 0;                                  # set this to the IP of the 
Director
my $fdport = 9102;
my ($host, %opts);

die "ERROR:  You must set the broadcast and my_ip addresses before running this 
tool" unless ($my_ip);

Getopt::Long::Configure ("bundling");
GetOptions(\%opts,
           'interval|i=i',
           'retry|r',
           'verbose|v+',
           'wake|w');

$host = shift || die "No host specified!\n";

$ret = check_host($host) if ($host);
print "Client $host not found on network\n" if ($ret == 2);
print "Returning value $ret\n" if ($opts{verbose} > 1);

exit ($ret);


sub check_host
{
    my $host = $_[0];
    my ($hostname, $p, $retrycount);

    if ($host =~ /^\d+\.\d+\.\d+\.\d+$/)
    {
        my $ip = inet_aton($host);
        $hostname = gethostbyaddr($ip, AF_INET);
        print "Host $host has name $hostname\n" if ($opts{verbose});
    }
    else
    {
        $hostname = $host;
        printf("Client $host has address %d.%d.%d.%d\n",
               unpack('C4',
                      (my @addrs = (gethostbyname($host))[4])[0])) if 
($opts{verbose} > 1);
    }

    if ($opts{wake})
    {
        foreach my $ether (split(/\s+/, get_ethers_by_host(shift())))
        {
            wake_host_by_hwaddr($ether);
        }
        printf("Wake-on-LAN packet sent to $host; sleeping for %s seconds\n",
               $opts{interval} || 30) if ($opts{verbose});
        sleep ($opts{interval} || 30);
    }


    $p = Net::Ping->new("icmp");
    $p->bind($my_ip);

    $retrycount = ($opts{retry} ? 3 : 1);

    while ($retrycount && ($ret != 0))
    {
        if ($p->ping($host, 2))
        {
            print "Host $host is alive\n" if ($opts{verbose});
            
            my ($fdsocket, $buf);
            
            $fdsocket = IO::Socket::INET->new(PeerAddr => $host,
                                              PeerPort => $fdport,
                                              Proto    => 'tcp') || die "Socket 
$host:$fdport: $!";
            $fdsocket->send(pack("N/a","Ping")) || die "Send: $!";
            $buf = <$fdsocket> || die "Recv: $!";
            $fdsocket->close() || die "Cannot close socket: $!";
            
            if ($buf =~ /2000 Ping OK/)
            {
                print "Bacula-FD is listening on $host:$fdport\n" if 
($opts{verbose});
                $ret = 0;
            }
            else
            {
                print "Bacula-FD is not running on host $host\n";
                $ret = 1;
            }
        }
        else
        {
            $ret = 2;
        }
        $retrycount--;
        if ($opts{retry} && ($ret != 0))
        {
            printf("\tNot found on try %d",
                   3 - $retrycount) if ($opts{verbose});
            if ($retrycount)
            {
                printf("; sleeping for %s seconds before retrying\n",
                       $opts{interval} || 30) if ($opts{verbose});
                sleep($opts{interval} || 30);
            }
            else
            {
                print "\n" if ($opts{verbose});
            }
        }
    }
    $p->close();

    return ($ret);
}



sub wake_host_by_hwaddr
{
    my ($packet, $hwaddr, $raddr, $port);

    foreach (split /:/, $_[0])
    {
        $hwaddr .= chr(hex($_));
    }
    $packet = chr(0xFF) x 6 . $hwaddr x 16;
    
    socket((SOCK, AF_INET, SOCK_DGRAM, getprotobyname('udp'))) || die "socket : 
$!";
    setsockopt(SOCK, SOL_SOCKET, SO_BROADCAST, 1) || die "setsockopt : $!";

    $port = getservbyname('discard', 'udp');
    $raddr = gethostbyname($broadcast);
    send(SOCK, $packet, 0, pack_sockaddr_in($port, $raddr)) || die "send : $!";

    close (SOCK);

    return;
}


sub get_ethers_by_host
{
    my ($dbh, $sth, $query, $ethers);

    $dbh = open_db();
    $query = sprintf('SELECT ethers FROM ethers WHERE hostname = \'%s\'',
                     $_[0]);
    $sth = $dbh->prepare($query);
    $sth->execute || die "Error:" . $dbh->errstr . "\n";
    while (my $ref = $sth->fetchrow_arrayref)
    {
        $ethers = $$ref[0];
    }
    $sth->finish();
    $dbh->disconnect();

    return ($ethers);
}


sub open_db
{
    my $home = $ENV{'HOME'};
    my ($dsn, $dbh, $user, $password);

    $dsn = "DBI:mysql:database=$etherdb;"
         . "mysql_read_default_group=$ethergroup;"
         . "mysql_read_default_file=$home/.my.cnf;"
         . "mysql_mysql_client_found_rows=TRUE;"
         . "mysql_mysql_ssl=TRUE";

    $dbh = DBI->connect($dsn, undef, undef, {RaiseError => 1, AutoCommit => 0});

    return ($dbh);
}
_______________________________________________
Bacula-devel mailing list
Bacula-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bacula-devel

Reply via email to