[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Lightweight DNS server for Spamikaze
Hi,
This is a first stab at a real time DNSBL for Spamikaze. Not tested to
well yet, but seems to work OK. Feel free to include in Spamikaze if
deemed worthy.
Regards,
M4
#!/usr/bin/perl
#
# Copyright (C) 2007 Martijn Lievaart <m@xxxxxxx>
# Released under the GNU GPL
#
# NO WARRANTY, see the file COPYING for details.
#
# This file someday may be part of the spamikaze project:
# http://spamikaze.nl.linux.org/
#
# DNSBL daemon to query the spamikaze database in real-time
#
# Add something like this to your named.conf
#
# zone "skbl.example.com" IN {
# type forward;
# forward only;
# forwarders {
# 192.168.1.200 port 1234;
# };
# };
#
#
# Tested on Linux. Probably works on most Un*xes.
# Probably does not work on Windows.
#
use 5.007_003; # need safe signals
use Net::DNS;
use Net::DNS::Nameserver;
use Socket;
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin";
use lib '/opt/spamikaze/scripts'; # for development
use Spamikaze;
use Getopt::Long;
use File::Basename;
use File::Spec::Functions;
use POSIX 'setsid';
use Fcntl qw(LOCK_EX LOCK_NB);
my $programname = basename($0);
my $SELF = catfile $FindBin::Bin, $programname;
my $pidfile = '/var/run/skbld/skbld.pid';
my $help = 0;
my $foreground = 0;
my $verbose = 0;
my $debug = 0;
my $ttl;
# these should go into the main spamikaze config
my $port = 1234;
my $user;
my $group;
my @org_argv = @ARGV;
my $result =
GetOptions ("help|h" => \$help,
"verbose|v" => \$verbose,
"foreground|f" => \$foreground,
"debug|d" => \$debug,
"port|p=i" => \$port,
"ttl|t=i" => \$ttl,
"group|g=s" => \$group,
"user|u=s" => \$user,
);
usage($result) if (not $result or $help);
$verbose |= $debug;
$foreground |= $debug;
unless (defined($ttl)) {
$ttl = $debug ? 0 : $Spamikaze::dnsbl_ttl;
}
# Create listening socket first, so we can listen on port 53 before
# dropping privs.
my $ns =
Net::DNS::Nameserver->new(LocalPort => $port,
ReplyHandler => \&reply_handler,
Verbose => $debug, # verbose is to verbose :-)
)
or die "couldn't create nameserver object\n";
# Use of global filehandle intentional!
open(SELFLOCK, "<$0") or die("Couldn't open $0: $!\n");
flock(SELFLOCK, LOCK_EX | LOCK_NB) or die("Aborting: another $programname is already running\n");
if ($group) {
my $gid = getgrnam($user);
if (defined($gid)) {
$) = $gid;
warn "Cannot switch to group $group: $!\n" if $!;
} else {
warn "User $group does not exist.\n";
}
}
if ($user) {
my $uid = getpwnam($user);
if (defined($uid)) {
$> = $uid;
warn "Cannot switch to user $user: $!\n" if $!;
} else {
warn "User $user does not exist.\n";
}
}
# See if we can connect to the DB. If not, we can give an error before
# we daemonize.
my $dbh = Spamikaze::DBConnect();
unless ($foreground) {
print "Deamonizing...\n" if $verbose;
if (my $pid = daemonize()) {
# parent
print "Daemonized\n" if $debug;
$dbh->{InactiveDestroy} = 1;
exit;
}
writepid($$);
}
print "$programname ready to answer queries on port $port.\n";
$0 = "$programname on port $port";
$SIG{'INT'} = \&siginthandler;
#$SIG{'HUP'} = \&sighuphandler; # perldoc perlipc recommends the following:
my $sigset = POSIX::SigSet->new();
my $action = POSIX::SigAction->new('sighuphandler',
$sigset,
&POSIX::SA_NODEFER);
POSIX::sigaction(&POSIX::SIGHUP, $action);
eval {
$ns->main_loop();
};
if ($@ =~ "^OutOfHere at ") {
print "$programname terminating.\n";
} else {
print "$programname terminating due to unknown reason.\n";
print "'$@'\n" if $@;
}
unless ($foreground) {
unlink $pidfile or warn "Cannot unlink $pidfile";
}
exit;
#######################################################################
sub siginthandler {
die "OutOfHere";
}
sub sighuphandler {
print "got SIGHUP\n";
exec($SELF, @org_argv) or die "Couldn't restart: $!\n";
}
sub writepid {
my $pid = shift;
if (open my $fh, ">$pidfile") {
print $fh "$pid\n";
} else {
warn "can't open $pidfile: $!\n";
}
}
sub daemonize {
chdir '/' or die "Can't chdir to /: $!";
# Get ready to daemonize by redirecting our output to syslog, requesting that logger prefix the lines with our program name:
open(STDOUT, "|-", "logger -t $programname") or die("Couldn't open logger output stream: $!\n");
open(STDERR, ">&STDOUT") or die("Couldn't redirect STDERR to STDOUT: $!\n");
$| = 1; # Make output line-buffered so it will be flushed to syslog faster
open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
defined(my $pid = fork) or die "Can't fork: $!";
return $pid if $pid;
setsid or die "Can't start a new session: $!";
umask 0;
return $pid;
}
sub reply_handler {
my ($qname, $qclass, $qtype, $peerhost) = @_;
my ($rcode, @ans, @auth, @add);
$verbose and print "request for $qname/$qtype\n";
if ($qtype eq "A" or $qtype eq "TXT") {
if ($qname =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)\./) {
my ($octd, $octc, $octb, $octa) = ($1, $2, $3, $4);
my $ip = "$4.$3.$2.$1";
my $sql = "SELECT 1 FROM ipnumbers WHERE visible = 1
AND octa=$octa AND octb=$octb AND octc=$octc AND octd=$octd";
my $sth = $dbh->prepare($sql);
$sth->execute();
if ($sth->fetch) {
SWITCH: for ($qtype) {
/^A$/ and do {
push @ans, Net::DNS::RR->new("$qname $ttl $qclass $qtype 127.0.0.1");
};
/^TXT$/ and do {
my $dnsbl_url_base = $Spamikaze::dnsbl_url_base;
push @ans, Net::DNS::RR->new("$qname $ttl $qclass $qtype \"$dnsbl_url_base$ip\"");
};
}
$debug and print "Spam IP: $ip\n";
$rcode = "NOERROR";
} elsif ($sth->err) {
warn $sth->errstr();
$rcode = "SERVFAIL";
} else {
$debug and print "Ham IP: $ip\n";
$rcode = "NXDOMAIN";
}
} else {
$rcode = "FORMERR";
}
} else {
$rcode = "NOTIMP";
}
# mark the answer as authoritive (by setting the aa flag
return ($rcode, \@ans, \@auth, \@add, { aa => 1 });
}
sub usage {
my $rc = shift;
print <<EOD;
Usage: $programname [options]
SpamiKaze BlackList Daemon, a light weight DNS server that
serves a spamikaze zone.
Options:
help|h Print this help.
verbose|v Be more verbose.
debug|d Gobs of debugging info (implies -v and -f).
foreground|f Don\'t daemonize.
port|p Port to listen on (default 1234)
ttl|t TTL to use for answers. Default is 0 if -d,
taken from spamikaze inifile otherwise.
user|u Switch to this user after binding to socket.
group|g Switch to this group after binding to socket.
EOD
exit($rc);
}