[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);
}