[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: Lightweight DNS server for Spamikaze
Martijn Lievaart wrote:
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.
Found the first bug, SIGHUP didn't work if running as another user.
Also a redhat-style initscript.
Cheers,
M4
#! /bin/bash
#
# skbld Start/Stop the skbl daemon.
#
# chkconfig: 2345 90 60
# description: skbld is a leightweight DNS server for Spamikaze.
# processname: skbld
# config:
# pidfile: /var/run/skbld.pid
# Source function library.
. /etc/init.d/functions
. /etc/sysconfig/skbld
# See how we were called.
prog="skbld"
if [ -n "SKBLD" ]; then
for d in /sbin /bin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin; do
[ -x "$d/skbld" ] && SKBLD="$d/skbld"
done
fi
start() {
echo -n $"Starting $prog: "
if [ -e /var/lock/subsys/skbld ]; then
if [ -e /var/run/skbld.pid ] && [ -e /proc/`cat /var/run/skbld.pid` ]; then
echo -n $"cannot start skbld: skbld is already running.";
failure $"cannot start skbld: skbld already running.";
echo
return 1
fi
fi
daemon $SKBLD $SKBLDARGS
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/skbld;
return $RETVAL
}
stop() {
echo -n $"Stopping $prog: "
if [ ! -e /var/lock/subsys/skbld ]; then
echo -n $"cannot stop skbld: skbld is not running."
failure $"cannot stop skbld: skbld is not running."
echo
return 1;
fi
killproc skbld
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/skbld;
return $RETVAL
}
rhstatus() {
status skbld
}
restart() {
stop
start
}
reload() {
echo -n $"Reloading skbl daemon configuration: "
killproc skbld -HUP
RETVAL=$?
echo
return $RETVAL
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
reload)
reload
;;
status)
rhstatus
;;
condrestart)
[ -f /var/lock/subsys/skbld ] && restart || :
;;
*)
echo $"Usage: $0 {start|stop|status|reload|restart|condrestart}"
exit 1
esac
SKBLDARGS="-u spamikaze -g spamikaze"
SKBLD=/home/martijn/projects/spamikaze/skbld
#!/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);
}