/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2006-2011 The OpenNMS Group, Inc.
* OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc.
*
* OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
*
* OpenNMS(R) 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.
*
* OpenNMS(R) 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 OpenNMS(R). If not, see:
* http://www.gnu.org/licenses/
*
* For more information contact:
* OpenNMS(R) Licensing <license@opennms.org>
* http://www.opennms.org/
* http://www.opennms.com/
*******************************************************************************/
package org.opennms.netmgt.vulnscand;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.InetAddress;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.ValidationException;
import org.opennms.core.utils.DBUtils;
import org.opennms.core.utils.InetAddressUtils;
import org.opennms.core.utils.ThreadCategory;
import org.opennms.netmgt.config.DataSourceFactory;
import org.opennms.netmgt.config.VulnscandConfigFactory;
import org.opennms.netmgt.config.vulnscand.ScanLevel;
import org.opennms.netmgt.config.vulnscand.VulnscandConfiguration;
import org.opennms.netmgt.daemon.AbstractServiceDaemon;
/**
* <P>
* Vulnerability scanning daemon. This process is used to provide continual
* scans of target interfaces that identify possible security holes. The
* vulnerability scanner that this version uses to identify security flaws is
* Nessus 1.1.X (www.nessus.org).
* </P>
*
* <P>
* This code is adapted from the capsd code because its behavior is quite
* similar; it continually scans the target ranges, enters the scan results into
* a database table, and also supports rescans whose threads are pulled from a
* separate thread pool so that they occur immediately.
* </P>
*
* @author <A HREF="mailto:seth@opennms.org">Seth Leger </A>
* @author <A HREF="http://www.opennms.org/">OpenNMS </A>
*/
public class Vulnscand extends AbstractServiceDaemon {
/**
* Singleton instance of the Vulnscand class
*/
private static final Vulnscand m_singleton = new Vulnscand();
/**
* Database synchronization lock for synchronizing write access to the
* database between the SpecificScanProcessor and RescanProcessor thread
* pools
*/
private static final Object m_dbSyncLock = new Object();
/**
* <P>
* Contains dotted-decimal representation of the IP address where Vulnscand
* is running. Used when vulnscand sends events out
* </P>
*/
private static final String m_address = InetAddressUtils.getLocalHostAddressAsString();
/**
* Rescan scheduler thread
*/
private Scheduler m_scheduler;
/**
* Event receiver.
*/
private BroadcastEventProcessor m_receiver;
/**
* The pool of threads that are used to executed the SpecificScanProcessor
* instances queued by the event processor (BroadcastEventProcessor).
*/
private ExecutorService m_specificScanRunner;
/**
* The pool of threads that are used to executed RescanProcessor instances
* queued by the rescan scheduler thread.
*/
private ExecutorService m_scheduledScanRunner;
/**
* SQL used to retrieve the last poll time for all the managed interfaces
* belonging to a particular node.
*/
static final String SQL_GET_LAST_POLL_TIME = "SELECT lastAttemptTime FROM vulnerabilities WHERE ipaddr=? ORDER BY lastAttemptTime DESC";
/**
* The SQL statement used to retrieve all non-deleted/non-forced unamanaged
* IP interfaces from the 'ipInterface' table.
*/
static final String SQL_DB_RETRIEVE_IP_INTERFACE = "SELECT ipaddr FROM ipinterface WHERE ipaddr!='0.0.0.0' AND isManaged!='D' AND isManaged!='F'";
/**
* Constructs the Vulnscand objec
*/
public Vulnscand() {
super("OpenNMS.Vulnscand");
m_scheduler = null;
}
/**
* <p>onStop</p>
*/
protected void onStop() {
// Stop the broadcast event receiver
//
m_receiver.close();
// Stop the Suspect Event Processor thread pool
//
m_specificScanRunner.shutdown();
// Stop the Rescan Processor thread pool
//
m_scheduledScanRunner.shutdown();
}
/**
* <p>onStart</p>
*/
protected void onStart() {
// Initialize the Vulnscand configuration factory.
//
try {
VulnscandConfigFactory.reload();
} catch (MarshalException ex) {
log().error("Failed to load Vulnscand configuration", ex);
throw new UndeclaredThrowableException(ex);
} catch (ValidationException ex) {
log().error("Failed to load Vulnscand configuration", ex);
throw new UndeclaredThrowableException(ex);
} catch (IOException ex) {
log().error("Failed to load Vulnscand configuration", ex);
throw new UndeclaredThrowableException(ex);
}
// Initialize the Database configuration factory
//
try {
DataSourceFactory.init();
} catch (IOException ie) {
log().fatal("IOException loading database config", ie);
throw new UndeclaredThrowableException(ie);
} catch (MarshalException me) {
log().fatal("Marshall Exception loading database config", me);
throw new UndeclaredThrowableException(me);
} catch (ValidationException ve) {
log().fatal("Validation Exception loading database config", ve);
throw new UndeclaredThrowableException(ve);
} catch (ClassNotFoundException ce) {
log().fatal("Class lookup failure loading database config", ce);
throw new UndeclaredThrowableException(ce);
} catch (PropertyVetoException e) {
log().fatal("Property Veto Exception loading database config", e);
throw new UndeclaredThrowableException(e);
} catch (SQLException e) {
log().fatal("SQL Exception loading database config", e);
throw new UndeclaredThrowableException(e);
}
// Create the specific and scheduled scan pools
//
m_specificScanRunner = Executors.newFixedThreadPool(VulnscandConfigFactory.getInstance().getMaxSuspectThreadPoolSize());
m_scheduledScanRunner = Executors.newFixedThreadPool(VulnscandConfigFactory.getInstance().getMaxRescanThreadPoolSize());
// Create and start the rescan scheduler
//
if (log().isDebugEnabled())
log().debug("start: Creating rescan scheduler");
try {
// During instantiation, the scheduler will load the
// list of known nodes from the database
m_scheduler = new Scheduler(m_scheduledScanRunner);
initialize();
} catch (SQLException sqlE) {
log().error("Failed to initialize the rescan scheduler.", sqlE);
throw new UndeclaredThrowableException(sqlE);
} catch (Throwable t) {
log().error("Failed to initialize the rescan scheduler.", t);
//throw new UndeclaredThrowableException(t);
}
m_scheduler.start();
// Create an event receiver.
//
try {
if (log().isDebugEnabled())
log().debug("start: Creating event broadcast event receiver");
m_receiver = new BroadcastEventProcessor(m_specificScanRunner, m_scheduler);
} catch (Throwable t) {
log().error("Failed to initialized the broadcast event receiver", t);
throw new UndeclaredThrowableException(t);
}
}
/**
* Used to retrieve the local host name/address. The name/address of the
* machine on which Vulnscand is running.
*
* @return a {@link java.lang.String} object.
*/
public static String getLocalHostAddress() {
return m_address;
}
/**
* <p>getInstance</p>
*
* @return a {@link org.opennms.netmgt.vulnscand.Vulnscand} object.
*/
public static Vulnscand getInstance() {
return m_singleton;
}
static Object getDbSyncLock() {
return m_dbSyncLock;
}
/**
* <p>onInit</p>
*/
protected void onInit() {
}
void setInitialScheduleSleep() {
//
m_scheduler.setInitialSleep(VulnscandConfigFactory.getInstance().getInitialSleepTime());
if (log().isDebugEnabled())
log().debug("Scheduler: initial rescan sleep time(millis): " + m_scheduler.getInitialSleep());
}
/**
* Creates a NessusScanConfiguration object representing the specified node
* and adds it to the known node list for scheduling.
*
* @param address
* the internet address.
* @param scanLevel
* the scan level.
* @param scheduler TODO
* @throws SQLException
* if there is any problem accessing the database
*/
void addToKnownAddresses(InetAddress address, int scanLevel) throws SQLException {
// Retrieve last poll time for the node from the ipInterface
// table.
Connection db = null;
final DBUtils d = new DBUtils(getClass());
try {
db = DataSourceFactory.getInstance().getConnection();
d.watch(db);
PreparedStatement ifStmt = db.prepareStatement(Vulnscand.SQL_GET_LAST_POLL_TIME);
d.watch(ifStmt);
ifStmt.setString(1, address.getHostAddress());
ResultSet rset = ifStmt.executeQuery();
d.watch(rset);
ThreadCategory log = log();
if (rset.next()) {
Timestamp lastPolled = rset.getTimestamp(1);
if (lastPolled != null && rset.wasNull() == false) {
if (log.isDebugEnabled())
log.debug("scheduleAddress: adding node " + address + " with last poll time " + lastPolled);
//try {
m_scheduler.schedule(address, new NessusScanConfiguration(address, scanLevel, lastPolled, getInterval()));
//} catch (UnknownHostException ex) {
// log.error("Could not add invalid address to schedule: " + address, ex);
//}
}
} else {
if (log.isDebugEnabled())
log.debug("scheduleAddress: adding ipAddr " + address + " with no previous poll");
m_scheduler.schedule(address, new NessusScanConfiguration(address, scanLevel, new Timestamp(0), getInterval()));
}
} finally {
d.cleanUp();
}
}
private long getInterval() {
return VulnscandConfigFactory.getInstance().getRescanFrequency();
}
Set<String> getAllManagedInterfaces() {
Set<String> retval = new TreeSet<String>();
String addressString = null;
Connection connection = null;
Statement selectInterfaces = null;
ResultSet interfaces = null;
try {
connection = DataSourceFactory.getInstance().getConnection();
selectInterfaces = connection.createStatement();
interfaces = selectInterfaces.executeQuery(Vulnscand.SQL_DB_RETRIEVE_IP_INTERFACE);
int i = 0;
while (interfaces.next()) {
addressString = interfaces.getString(1);
if (addressString != null) {
//addressString = addressString.replaceAll("/", "");
//addressString = addressString + "/" + addressString;
retval.add(addressString);
m_scheduler.log().debug("JOHAN: " + addressString);
} else {
m_scheduler.log().warn("UNEXPECTED CONDITION: NULL string in the results of the query for managed interfaces from the ipinterface table.");
}
i++;
}
m_scheduler.log().info("Loaded " + i + " managed interfaces from the database.");
} catch (SQLException ex) {
m_scheduler.log().error(ex.getLocalizedMessage(), ex);
} finally {
try {
if (interfaces != null)
interfaces.close();
if (selectInterfaces != null)
selectInterfaces.close();
} catch (Throwable ex) {
} finally {
try {
if (connection != null)
connection.close();
} catch (Throwable e) {
}
}
}
return retval;
}
void scheduleExistingInterfaces() throws SQLException {
// Load the list of IP addresses from the config file and schedule
// them in the appropriate level
VulnscandConfigFactory configFactory = VulnscandConfigFactory.getInstance();
VulnscandConfiguration config = VulnscandConfigFactory.getConfiguration();
// If the status of the daemon is "true" (meaning "on")...
if (config.getStatus()) {
Enumeration<ScanLevel> scanLevels = config.enumerateScanLevel();
while (scanLevels.hasMoreElements()) {
ScanLevel scanLevel = scanLevels.nextElement();
int level = scanLevel.getLevel();
// Grab the list of included addresses for this level
//Set levelAddresses = configFactory.getAllIpAddresses(scanLevel);
Set<String> levelAddresses = new TreeSet<String>();
// If scanning of the managed IPs is enabled...
if (configFactory.getManagedInterfacesStatus()) {
// And the managed IPs are set to be scanned at the current
// level...
if (configFactory.getManagedInterfacesScanLevel() == level) {
// Then schedule those puppies to be scanned
levelAddresses.addAll(getAllManagedInterfaces());
log().info("Scheduled the managed interfaces at scan level " + level + ".");
}
}
// Remove all of the excluded addresses (the excluded
// addresses are cached, so this operation is lighter
// than constructing the exclusion list each time)
// JOHAN - THINK....
levelAddresses.removeAll(configFactory.getAllExcludes());
log().info("Adding " + levelAddresses.size() + " addresses to the vulnerability scan scheduler.");
Iterator<String> itr = levelAddresses.iterator();
while (itr.hasNext()) {
String next = itr.next();
String nextAddress = null;
nextAddress = (String) next;
// REMOVE SLASHES.... -
//nextAddress = nextAddress.replaceAll("/", "");
//nextAddress = nextAddress + "/" + nextAddress;
log().debug("JOHAN LevelAddresses : " + nextAddress);
// All we know right now is the IP.....
InetAddress frump = InetAddressUtils.addr(nextAddress);
addToKnownAddresses(frump, level);
}
}
} else {
log().info("Vulnerability scanning is DISABLED.");
}
}
/**
* <p>initialize</p>
*
* @throws java.sql.SQLException if any.
*/
public void initialize() throws SQLException {
// Get rescan interval from configuration factory
setInitialScheduleSleep();
scheduleExistingInterfaces();
}
} // end Vulnscand class