/*******************************************************************************
* 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.dns;
import java.net.InetAddress;
import org.jwall.rbl.RblServer;
import org.jwall.rbl.data.RBListEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>
*
* </p>
*
* @author chris
*
*/
public class QueryHandler {
static Logger log = LoggerFactory.getLogger( QueryHandler.class );
/** This pattern is used to check for actions in DNS query values */
String pattern = ".*(block-\\d+|unblock)\\.rbl\\.localnet$";
/** The for which this handler processes queries */
String domain = "rbl.localnet";
/** The rbl server to which this handler belongs */
RblServer server;
public QueryHandler( RblServer server ){
this.server = server;
domain = server.getDomain();
pattern = ".*(block-\\d+|unblock)\\." + domain + "$";
}
public synchronized Response process( InetAddress source, Query q ){
Response response = new Response( q );
for( QuerySection sect : q.getSections() ){
String queryValue = sect.getQName();
log.debug( "Received query for '{}'", queryValue );
if( !queryValue.endsWith( "." ) && !queryValue.endsWith( domain ) )
queryValue = queryValue + "." + domain;
if( sect.getQClass() == QuerySection.QUERY_TYPE_PTR ){
RBListEntry entry = server.getBlockList().lookup( queryValue );
if( entry != null && ! entry.isExpired() ){
response.add( new PtrRecord( queryValue, queryValue, entry.getRemainingLifetime() ) );
} else {
response.setStatus( Response.RC_NAME_ERROR );
break;
}
}
if( sect.getQType() == QuerySection.QUERY_TYPE_A ){
queryValue = this.preprocess( source, queryValue );
RBListEntry entry = server.getBlockList().lookup( queryValue );
if( entry != null && !entry.isExpired() ){
log.debug( "Entry '{}' is on block list and will expire in {} seconds", queryValue, entry.getRemainingLifetime() );
response.add( new ARecord( queryValue, RblServer.BLOCKED_VALUE, entry.getRemainingLifetime() ) );
} else {
log.debug( "Removing expired entry..." );
response.setStatus( Response.RC_NAME_ERROR );
if( entry != null )
server.getBlockList().remove( entry.getName() );
}
} else {
response.setStatus( Response.RC_NAME_ERROR );
}
}
return response;
}
/**
* <p>
* This method checks for any actions, which may be contained in the
* query-value. The actions are processed and removed from the query-value,
* yielding the final block-entry key.
* </p>
*
* @param source The source address from which this query has been sent
* @param query The query value of the DNS query
* @return
*/
protected String preprocess( InetAddress source, String queryValue ){
if( !queryValue.matches( pattern ) )
return queryValue;
int idx = queryValue.lastIndexOf( "." + domain );
String part = queryValue.substring( 0, idx );
int start = part.lastIndexOf( "." );
String action = part.substring( start );
// make 'key' the final query-value by removing the action value
//
String key = queryValue.replaceFirst( action, "" );
// process the actions...
//
if( action.startsWith(".block-") ){
// check permissions
if( ! server.getRblSecurityManager().hasPermission( source, RblSecurityManager.BLOCK_PERMISSION ) ){
log.error( "No permission for action 'block' for address '{}'", source.getHostAddress() );
return key;
}
try {
Integer ttl = new Integer( action.substring( 7 ) );
log.debug( "Need to block key: '{}' for {} seconds", key, ttl );
server.block( key, ttl );
queryValue = key;
} catch (Exception e) {
log.error( "Failed to block entry: '{}': {}", queryValue, e.getMessage() );
if( log.isDebugEnabled() )
e.printStackTrace();
}
return queryValue;
}
// handle .unblock.domain queries, which unblock the corresponding entry
//
if( action.equals( ".unblock" ) ){
// check permissions
if( ! server.getRblSecurityManager().hasPermission( source, RblSecurityManager.UNBLOCK_PERMISSION ) ){
log.error( "No permission for action 'block' for address '{}'", source.getHostAddress() );
return key;
}
server.unblock( key );
}
return key;
}
/**
* <p>
* To speed up lookups, RBLs are stored as Hashmaps. Usually, queries contain an address
* value in the form
* </p>
* <pre>
* 1.0.16.172.rbl.localnet
* </pre>
* </p>
* <p>
* This method will convert addresses like <code>172.16.0.1</code> into this reversed format
* and append the specified domain.
* </p>
* <p>
* If the address already ends with the given domain, this method assumes the address value
* to already match the reversed format and will simply return it as is.
* </p>
*
* @param address
* @param domain
* @return
*/
public static String getKeyForAddress( String address, String domain ){
// if the input already in reverse-domain format, simply return it
//
if( address.endsWith( domain ) )
return address;
// otherwise we need to reverse the addresses octets and append
// the domain name
//
StringBuffer key = new StringBuffer( address );
String[] tok = address.split( "\\." );
key = new StringBuffer();
for( int i = tok.length - 1; i >= 0; i--){
key.append( tok[i] );
if( i > 0 )
key.append( "." );
}
// append the domain name
//
if( !domain.startsWith( "." ) )
key.append( "." );
key.append( domain );
return key.toString();
}
}