/*******************************************************************************
* Copyright (C) 2010 Christian Bockermann <chris@jwall.org>
*
* This file is part of the jwall-rbld program. jwall-rbld is an implementation
* of a simple DNS server for running a local, customized real time block-list.
* More information and documentation for the jwall-rbld can be found at
*
* http://www.jwall.org/jwall-rbld
*
* 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 of the License, 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 <http://www.gnu.org/licenses/>.
******************************************************************************/
package org.jwall.rbl;
import java.io.File;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.util.Properties;
import org.jwall.rbl.data.RBList;
import org.jwall.rbl.data.RBListEntry;
import org.jwall.rbl.data.RblFile;
import org.jwall.rbl.data.RblSettings;
import org.jwall.rbl.dns.Query;
import org.jwall.rbl.dns.QueryHandler;
import org.jwall.rbl.dns.RblSecurityManager;
import org.jwall.rbl.dns.Response;
import org.jwall.rbl.net.AdminHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>
* This class implements a very simple DNS server for running a real-time
* blackhole list. The server only responds to A records. Support for AAAA
* record queries is planned.
* </p>
*
* @author Christian Bockermann <chris@jwall.org>
*
*/
public class RblServer extends Thread {
public final static String RBL_HOME = "RBL_HOME";
public final static String RBL_PORT = "rbl.port";
public final static String RBL_ADDRESS = "rbl.address";
public final static String RBL_ADMIN_PORT = "rbl.admin.port";
public final static String RBL_FILE = "rbl.file";
public final static String RBL_DOMAIN = "rbl.domain";
public final static String RBL_PERMISSIONS = "rbl.permission.file";
/**
* These properties can be overwritten using system properties (command
* line)
*/
public final static String[] PROPERTY_NAMES = { RBL_PORT, RBL_ADDRESS,
RBL_ADMIN_PORT, RBL_FILE, RBL_DOMAIN, RBL_PERMISSIONS };
public static Inet4Address BLOCKED_VALUE = null;
public final static String VERSION = "0.3";
static {
try {
BLOCKED_VALUE = (Inet4Address) InetAddress.getByName("127.0.0.1");
} catch (Exception e) {
}
if( System.getProperty( "rbl.log" ) == null ){
System.setProperty( "rbl.log", "/var/log/jwall-rbld.log");
}
}
/* The global logger for this class */
public Logger log = LoggerFactory.getLogger(RblServer.class);
/** The block list which is served by this server */
RBList rbl;
/** The DNS domain name which this server handles queries for */
String domain = "rbl.localnet";
/** The UDP server socket for receiving queries */
DatagramSocket socket;
long queryCount = 0L;
boolean running = true;
int adminPort = -1;
AdminHandler adminInterface;
/** This is the query handler, which creates responses for queries */
QueryHandler queryHandler;
/**
* This is a reference to the security manager, validating block/unblock
* request on IP basis...
*/
RblSecurityManager securityManager;
/**
* Create a new RblServer with the provided set of properties.
*
* @param p
* @throws Exception
*/
public RblServer(Properties p) throws Exception {
String addr = "127.0.0.1";
Integer port = 15353;
File rblFile = new File(File.separator + "var" + File.separator + "lib"
+ File.separator + "jwall-rbl" + File.separator + "local.rbl");
try {
if (p.getProperty("rbl.port") != null)
port = new Integer(p.getProperty("rbl.port"));
} catch (Exception e) {
throw new Exception("Invalid port '" + p.getProperty("rbl.port")
+ "' specified!");
}
try {
if (p.getProperty("rbl.address") != null)
addr = InetAddress.getByName(p.getProperty("rbl.address"))
.getHostAddress();
} catch (Exception e) {
throw new Exception("Failed to set address '"
+ p.getProperty("rbl.address") + "'!");
}
if (p.getProperty(RBL_DOMAIN) != null)
domain = p.getProperty(RBL_DOMAIN);
try {
if (p.getProperty("rbl.admin.port") != null)
adminPort = new Integer(p.getProperty("rbl.admin.port"));
} catch (Exception e) {
}
try {
if (p.getProperty(RBL_FILE) != null)
rblFile = new File(p.getProperty(RBL_FILE));
rbl = new RblFile(rblFile);
} catch (Exception e) {
throw new Exception("Failed to read rbl-list from file '" + "': "
+ e.getMessage());
}
File policyFile = new File(File.separator + "etc" + File.separator
+ "jwall-rbld.permissions");
try {
if (p.getProperty(RBL_PERMISSIONS) != null)
policyFile = new File(p.getProperty(RBL_PERMISSIONS));
if (policyFile.isFile())
RblSecurityManager.getInstance().readPermissions(policyFile);
else
log.debug(
"Permission file {} does not exist, updates of DNS is disabled.",
policyFile.getAbsolutePath());
} catch (Exception e) {
throw new Exception("Failed to load permissions from '"
+ policyFile.getAbsolutePath() + "': " + e.getMessage());
}
log.debug("Starting jwall-rbld version {} for domain '{}'", VERSION,
getDomain());
log.debug("Listening on UDP port {}:{}", addr, port);
queryHandler = new QueryHandler(this);
socket = new DatagramSocket(port, InetAddress.getByName(addr));
/*
* DatagramChannel channel = DatagramChannel.open(); socket =
* channel.socket(); channel.configureBlocking( true ); socket.connect(
* InetAddress.getByName( addr ), port );
*/
this.setDaemon(true);
}
/**
* This method creates a new RBL server listening at the specified address
* on the given port.
*
* @param addr
* @param port
* @throws Exception
*/
public RblServer(InetAddress addr, Integer port) throws Exception {
log.debug("Creating RblServer listening at {}, port {}",
addr.getHostAddress(), port);
queryHandler = new QueryHandler(this);
/*
* DatagramChannel channel = DatagramChannel.open(); socket =
* channel.socket(); channel.configureBlocking( true ); socket.connect(
* addr, port );
*/
socket = new DatagramSocket(port, addr);
}
public void setRblSecurityManager(RblSecurityManager manager) {
securityManager = manager;
}
public RblSecurityManager getRblSecurityManager() {
if (this.securityManager != null)
return securityManager;
return RblSecurityManager.getInstance();
}
/**
* Sets the block list for this server.
*
* @param list
*/
public void setBlockList(RBList list) {
this.rbl = list;
}
public RBList getBlockList() {
return this.rbl;
}
public String getDomain() {
return this.domain;
}
public void block(String address, Integer ttl) {
String key = QueryHandler.getKeyForAddress(address, getDomain());
log.debug("Adding rbl-entry with key = '{}'", key);
RBListEntry entry = new RBListEntry(null, key);
entry.setName(address);
entry.setCreated(System.currentTimeMillis());
entry.setLifetime(ttl);
getBlockList().add(entry);
}
public void unblock(String address) {
String key = QueryHandler.getKeyForAddress(address, getDomain());
log.debug("Removing rbl-entry with key = '{}'", key);
getBlockList().remove(key);
}
/**
* @see java.lang.Thread#run()
*/
@Override
public void run() {
try {
if (adminPort > 0) {
log.debug("Starting admin interface on port {}", adminPort);
adminInterface = new AdminHandler(this, "localhost", adminPort);
adminInterface.start();
} else
log.debug("No admin-port defined, not starting admin interface!");
} catch (Exception e) {
e.printStackTrace();
}
try {
socket.setSoTimeout(1000);
} catch (Exception e) {
}
while (running) {
try {
DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);
log.debug("Waiting for incoming DNS queries...");
socket.receive(packet);
log.debug("Received packet {}... size is {}",
packet.getAddress() + ":" + packet.getPort(),
packet.getLength());
byte[] data = packet.getData();
log.debug("Received packet of size {} bytes: {}", data.length,
new String(data));
Query q = Query.parse(data, 0);
Response response = queryHandler
.process(packet.getAddress(), q);
byte[] re = response.toByteArray();
DatagramPacket answer = new DatagramPacket(re, 0);
answer.setLength(re.length);
answer.setAddress(packet.getAddress());
answer.setPort(packet.getPort());
socket.send(answer);
queryCount++;
} catch (SocketTimeoutException ste) {
} catch (Exception e) {
e.printStackTrace();
}
}
log.info("Closing UDP socket...");
socket.close();
}
/**
* This method is called upon server shutdown, e.g. by the VM's
* ShutdownHook. It will save the
*
*/
public void shutdown() {
log.info("Received shutdown signal!");
rbl.store();
// if there is an admin handler running, we need to stop
// that one as well
//
if (adminInterface != null) {
adminInterface.shutdown();
try {
adminInterface.join(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("Setting running=false");
running = false;
log.info("Sending interrupt() signal...");
interrupt();
log.info("Disconnecting socket...");
socket.disconnect();
}
/**
* <p>
* This is the server's main entry point. It will read the configuration
* from <code>${RBL_HOME}/etc/jwall-rbld.conf</code> if that file exists or
* from a configuration file specified as first argument at the command
* line.
* </p>
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
String base = System.getenv(RBL_HOME);
if (base == null)
base = "";
String config = base + File.separator + "etc" + File.separator
+ "jwall-rbld.conf";
if (args.length > 0) {
config = args[0];
} else {
String[] etcs = new String[] { base + "/etc/jwall-rbld.conf",
"/etc/jwall-rbld.conf",
"/opt/modsecurity/etc/jwall-rbld.conf" };
for (String etc : etcs) {
File ef = new File(etc);
if (ef.exists()) {
config = ef.getAbsolutePath();
break;
}
}
}
final RblSettings p = RblSettings.read(config);
final RblServer server = new RblServer(p);
//
// register a shutdown hook which will fire as this process
// receives a TERM signal
//
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
server.shutdown();
}
});
server.log.debug("RblServer.run()");
server.run();
server.log.debug("RBL server exiting.");
}
}