/******************************************************************************* * 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.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; import java.util.HashSet; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; import java.util.StringTokenizer; import org.apache.regexp.RE; import org.opennms.core.queue.FifoQueue; import org.opennms.core.queue.FifoQueueException; import org.opennms.core.queue.FifoQueueImpl; 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.model.OnmsSeverity; /** * <p> * This class is a proxy for communications with the Nessus security scanner * daemon, nessusd. It has been designed to: * </p> * <ul> * <li>Initiate scans against a single target IP</li> * <li>Use prepackaged "levels" of security scanning: * <ul> * <li>Level 1: Portscanning</li> * <li>Level 2: Profiling</li> * <li>Level 3: Intrusions</li> * <li>Level 4: Destructive Intrusions</li> * </ul> * </li> * <li>Communicate with the daemon using username and and password, instead of * SSL key exchange</li> * </ul> * * <p> * Other functions (arbitrary plugin selection, SSL support) may be added in the * future. * </p> * */ class NessusScan implements Runnable { /* * Put the md5_caching directive in to prevent the server * from sending the entire plugin list at startup */ private static final String NTP_VERSION_STRING = "< NTP/1.2 >< md5_caching plugins_cve_id plugins_version>"; private static final String NTP_CLIENT_ENTITY = "CLIENT"; private static final String NTP_SERVER_ENTITY = "SERVER"; private static final String NTP_USERNAME_PROMPT = "User : "; private static final String NTP_PASSWORD_PROMPT = "Password : "; private static final String NTP_SEP = " <|> "; private static final int PORTSCAN_PLUGIN_ID = 0; /** Constant <code>SCAN_SUCCESS=0</code> */ public static final int SCAN_SUCCESS = 0; /** Constant <code>SCAN_HOST_DOWN=1</code> */ public static final int SCAN_HOST_DOWN = 1; /** Constant <code>SCAN_FATAL_ERROR=2</code> */ public static final int SCAN_FATAL_ERROR = 2; /** Constant <code>SCAN_NON_FATAL_ERROR=4</code> */ public static final int SCAN_NON_FATAL_ERROR = 4; /** Constant <code>SCAN_COMPLETE=8</code> */ public static final int SCAN_COMPLETE = 8; /** * Select the next vulnerabilityID from the sequence */ private static final String SELECT_NEXT_ID = "SELECT NEXTVAL('vulnNxtId')"; /** * Get all unresolved vulnerabilities for a given ipaddr. */ private static final String SELECT_ALL_VULNERABILITIES = "SELECT vulnerabilityid FROM vulnerabilities " + "WHERE ipaddr = ? AND resolvedtime IS NULL"; /** * This query retrieves the name and summary of a plugin out of the database * so that we can construct the logmsg of the vulnerability with the fields. */ private static final String SELECT_PLUGIN_INFO = "SELECT name, summary FROM vulnplugins " + "WHERE pluginid = ? AND pluginsubid = ?"; /** * Insert a new vulnerability into the "vulnerabilities" table */ private static final String INSERT_NEW_VULNERABILITY = "INSERT INTO vulnerabilities " + "(vulnerabilityid, nodeid, ipaddr, serviceid, creationtime, lastattempttime, lastscantime, " + "severity, pluginid, pluginsubid, logmsg, descr, port, protocol, cveentry) " + "VALUES (?,?,?,?, ?,?,?, ?, ?,?, ?,?, ?,?, ?)"; /** * Find an open vulnerability ID in the database. The combination of ipaddr, * port, protocol, pluginID, and pluginSubID is used as the key for * vulnerability uniqueness. */ private static final String SELECT_OPEN_VULNERABILITY = "SELECT vulnerabilityid FROM vulnerabilities " + "WHERE ipaddr = ? AND port = ? AND protocol = ? AND pluginid = ? AND pluginsubid = ? AND resolvedtime IS NULL"; /** * Update the timestamps in an open vulnerability that was rescanned and * still exists. */ private static final String VULNERABILITY_SCANNED = "UPDATE vulnerabilities SET lastattempttime = ?, lastscantime = ? " + "WHERE vulnerabilityid = ?"; /** * Update the timestamps in an open vulnerability for which the rescan * failed. */ private static final String VULNERABILITY_SCAN_ATTEMPTED = "UPDATE vulnerabilities SET lastattempttime = ? " + "WHERE vulnerabilityid = ?"; /** * Resolve a given vulnerability by its unique ID number. */ private static final String RESOLVE_VULNERABILITY = "UPDATE vulnerabilities SET lastattempttime = ?, resolvedtime = ? " + "WHERE vulnerabilityid = ?"; /** * Nessus configuration that is used to perform the current scan. */ private NessusScanConfiguration config; /** * Regex expression that is used to tokenize the messages from Nessus. */ private RE ntpTokenizer = null; /** * List of the open vulnerabilities on an IP address */ private Set<Integer> openVulnerabilities = new HashSet<Integer>(); /** * Array that holds the plugin lists for each scanning level. */ private String[] pluginLists = null; /** * Array that holds the safe-checks settings for each scanning level. */ private boolean[] safeChecks = null; /** * Integer of the ordinal of the last plugin that was executed against the * target. This number is used to ensure that if the scan is terminated * prematurely because of an unreachable host in the list, the * vulnerabilities are not marked resolved. */ private int lastPlugin; /** * Counter of the total number of plugins that will be executed against this * host. */ private int totalPlugins; /** * Create a new scan that will scan the target specified in the * configuration and insert the results of the scan into the database. * * @param newConfig a {@link org.opennms.netmgt.vulnscand.NessusScanConfiguration} object. * @throws java.lang.IllegalArgumentException if any. */ public NessusScan(NessusScanConfiguration newConfig) throws IllegalArgumentException { if (newConfig.isValid()) { config = newConfig; } else { throw new IllegalArgumentException("NessusScanConfiguration was invalid"); } try { ntpTokenizer = new RE(" <\\|> "); } catch (org.apache.regexp.RESyntaxException ex) { ThreadCategory.getInstance(NessusScan.class).error("FATAL ERROR in regex in NessusScan.java. Correct this error and rebuild.", ex); } init(); } private void init() { pluginLists = VulnscandConfigFactory.getInstance().getPluginLists(); safeChecks = VulnscandConfigFactory.getInstance().getSafeChecks(); lastPlugin = -1; totalPlugins = -1; } /** * <p>run</p> */ public void run() { ThreadCategory log = ThreadCategory.getInstance(getClass()); // Queue of the lines that are read from the Nessus socket FifoQueue<String> lines = null; // Flag that lets us know if we've found what we're looking for boolean found = false; // DB connection; is connected and disconnected as necessary Connection conn = null; /* * Grab the list of all current open vulnerabilities for the IP address. * We'll use this list to resolve vulnerabilities that are not * redetected. */ final DBUtils d = new DBUtils(getClass()); try { conn = DataSourceFactory.getInstance().getConnection(); d.watch(conn); } catch (SQLException ex) { log.error("Could not open DB connection", ex); return; } try { PreparedStatement stmt = conn.prepareStatement(SELECT_ALL_VULNERABILITIES); d.watch(stmt); stmt.setString(1, InetAddressUtils.str(config.targetAddress)); ResultSet openVulnerabilitiesRS = stmt.executeQuery(); d.watch(openVulnerabilitiesRS); while (openVulnerabilitiesRS.next()) { openVulnerabilities.add(new Integer(openVulnerabilitiesRS.getInt("vulnerabilityid"))); } } catch (SQLException ex) { log.error("Error when querying database for open vulnerabilities."); log.error(ex.getLocalizedMessage(), ex); return; } finally { d.cleanUp(); } /* * Perform a Nessus scan of the target IP address. As each * vulnerability is found, a new entry is put into the database * or the existing entry is updated. */ Socket nessusSocket = null; try { nessusSocket = NessusConnectionFactory.getConnection(config.hostname, config.hostport); if (nessusSocket == null) { throw new IOException("Factory returned null connection"); } InputStream in = nessusSocket.getInputStream(); OutputStream out = nessusSocket.getOutputStream(); log.debug("Attached streams to the Nessus socket."); // Login to the server out.write((NTP_VERSION_STRING + "\n").getBytes()); log.debug("Sent NTP version string."); lines = readLines(in); // Strip off the protocol/username prompt found = false; while (!found) { while (lines.size() > 0) { String line = (String) lines.remove(); log.debug("NTP string response: " + line); if (line.indexOf(NTP_USERNAME_PROMPT) != -1) { found = true; break; } } if (!found) { lines = readLines(in); } } // Login to the server out.write((config.username + "\n").getBytes()); log.debug("Sent username string."); // Strip off the password prompt found = false; while (!found) { while (lines.size() > 0) { String line = (String) lines.remove(); log.debug("Username response: " + line); if (line.indexOf(NTP_PASSWORD_PROMPT) != -1) { found = true; break; } } if (!found) { lines = readLines(in); } } // Login to the server out.write((config.password + "\n").getBytes()); log.debug("Sent password string."); // Strip off the rules list found = false; while (!found) { while (lines.size() > 0) { String line = (String) lines.remove(); // Do any necessary parsing log.debug("Password response: " + line); if (line.indexOf((NTP_SEP + NTP_SERVER_ENTITY).trim()) != -1) { found = true; break; } } if (!found) { lines = readLines(in); } } // Strip off the preferences list found = false; while (!found) { while (lines.size() > 0) { String line = (String) lines.remove(); // Do any necessary parsing log.debug("Preferences: " + line); if (line.indexOf((NTP_SEP + NTP_SERVER_ENTITY).trim()) != -1) { found = true; break; } } if (!found) { lines = readLines(in); } } // Strip off the rules list found = false; while (!found) { while (lines.size() > 0) { String line = (String) lines.remove(); // Do any necessary parsing log.debug("Rules: " + line); if (line.indexOf((NTP_SEP + NTP_SERVER_ENTITY).trim()) != -1) { found = true; break; } } if (!found) { lines = readLines(in); } } /* * Write the preferences list for the scan * (which includes the list of plugins to execute * against the target). */ out.write(buildPreferencesString().getBytes()); log.debug("Sent preferences string."); // Strip off the PREFERENCES_ERRORS found = false; while (!found) { while (lines.size() > 0) { String line = (String) lines.remove(); // Do any necessary parsing log.debug("Preferences response: " + line); if (line.indexOf((NTP_SEP + NTP_SERVER_ENTITY).trim()) != -1) { found = true; break; } } if (!found) lines = readLines(in); } /* * I'm using the NEW_ATTACK directive, since I don't * care about command strings getting too long (which * you would use the LONG_ATTACK directive for). */ //out.write((NTP_CLIENT_ENTITY + NTP_SEP + "NEW_ATTACK" + NTP_SEP + config.targetAddress.toString().replaceAll("/", "") + NTP_SEP + NTP_CLIENT_ENTITY + "\n").getBytes()); // out.write((NTP_CLIENT_ENTITY + NTP_SEP + "NEW_ATTACK" + NTP_SEP + config.targetAddress.toString().replaceAll("/", "") + NTP_SEP + NTP_CLIENT_ENTITY + "\n").getBytes()); out.write((NTP_CLIENT_ENTITY + NTP_SEP + "NEW_ATTACK" + NTP_SEP + config.targetAddress.getCanonicalHostName() + NTP_SEP + NTP_CLIENT_ENTITY + "\n").getBytes()); log.debug("Sent NEW_ATTACK directive against target: " + config.targetAddress.getCanonicalHostName()); // Read the response to the NEW_ATTACK int returnCode = SCAN_SUCCESS; while ((returnCode == SCAN_SUCCESS) || (returnCode == SCAN_NON_FATAL_ERROR)) { while (lines.size() > 0) { String line = (String) lines.remove(); log.debug("Nessus attack response: " + line.replace('\n', ' ')); // Grep out any inappropriate messages if ((line.indexOf("the server killed it") == -1)) { /* * This processing will update existing vulnerabilities * in the database and add new vulnerability entries * as the vulnerabilities are detected. */ returnCode = processScanMessage(line); } else { log.error("Discarded inappropriate Nessus message: " + line); } } // If the last read was successful, get more lines if ((returnCode == SCAN_SUCCESS) || (returnCode == SCAN_NON_FATAL_ERROR)) { lines = readLines(in); } } out.write(buildStopWholeTestString().getBytes()); /* * If there were open vulnerabilities that were not reconfirmed * during this scanning cycle, then mark them as resolved. */ if (openVulnerabilities.size() > 0) { try { conn = DataSourceFactory.getInstance().getConnection(); d.watch(conn); } catch (SQLException ex) { log.error("Could not open DB connection", ex); return; } try { PreparedStatement stmt = conn.prepareStatement(RESOLVE_VULNERABILITY); d.watch(stmt); Timestamp currentTime = new Timestamp(new java.util.Date().getTime()); Iterator<Integer> vuln = openVulnerabilities.iterator(); while (vuln.hasNext()) { stmt.setTimestamp(1, currentTime); /* * If the scan ended because of a successful completion * and all plugins were executed (indicating that the * host WAS accessible), resolve the bug. */ if ((returnCode == SCAN_COMPLETE) && (lastPlugin == totalPlugins)) { stmt.setTimestamp(2, currentTime); } // Otherwise, just leave the resolved field NULL else { stmt.setNull(2, Types.TIMESTAMP); } stmt.setInt(3, ((Integer) vuln.next()).intValue()); stmt.executeUpdate(); } } catch (SQLException ex) { log.error("Error when querying database for open vulnerabilities."); log.error(ex.getLocalizedMessage(), ex); return; } finally { d.cleanUp(); } } log.debug("Sent STOP_WHOLE_TEST directive against target " + config.targetAddress.toString()); } catch (FifoQueueException ex) { log.warn(ex.getMessage(), ex); } catch (InterruptedException ex) { log.warn(ex.getMessage(), ex); } catch (IOException ex) { log.warn(ex.getMessage(), ex); } finally { log.info("Releasing Nessus socket connection"); if (nessusSocket != null) { NessusConnectionFactory.releaseConnection(nessusSocket); } } // Update the scheduler flags for this configuration config.setScheduled(false); config.setLastScanned(new java.util.Date()); } private String buildStopWholeTestString() { return (NTP_CLIENT_ENTITY + NTP_SEP + "STOP_WHOLE_TEST" + NTP_SEP + NTP_CLIENT_ENTITY + "\n"); } private String buildStopScanString() { return (NTP_CLIENT_ENTITY + NTP_SEP + "STOP_ATTACK" + config.targetAddress.toString() + NTP_SEP + NTP_CLIENT_ENTITY + "\n"); } /** * Build the preferences string with the appropriate plugins and safe_checks * settings from the config file. */ private String buildPreferencesString() { String retval = NTP_CLIENT_ENTITY + NTP_SEP + "PREFERENCES" + NTP_SEP + "\n" + "plugin_set" + NTP_SEP + pluginLists[config.scanLevel] + "\n" + "safe_checks" + NTP_SEP; if (safeChecks[config.scanLevel]) retval += "yes"; else retval += "no"; retval += "\nmax_hosts" + NTP_SEP + "1\n" + "ntp_short_status" + NTP_SEP + "yes\n" + "reverse_lookup" + NTP_SEP + "yes\n" + NTP_SEP + NTP_CLIENT_ENTITY; return retval; } /** * Process a scan message. * <p> * This function is designed to parse any messages that come from Nessus * during a scan session (eg. after the NEW_ATTACK directive has been sent) * </p> * * <p> * The following types of events are handled: * <ul> * <li>Abbreviated STATUS messages</li> * <li>PORT</li> * <li>INFO</li> * <li>HOLE</li> * <li>BYE (as in BYE BYE)</li> * </ul> * </p> * */ private int processScanMessage(String message) { ThreadCategory log = ThreadCategory.getInstance(getClass()); int vulnerabilityId = -1; // DB connection; is connected and disconnected as necessary Connection conn = null; // Normal NTP messages if (message.startsWith((NTP_SERVER_ENTITY + NTP_SEP).trim())) { int i = 0; message = message.substring((NTP_SERVER_ENTITY + NTP_SEP).length()).trim(); String tokens[] = ntpTokenizer.split(message); String next = tokens[i++]; /* * Indicates information about the target system or * that a security hole has been located. */ if (next.equals("INFO") || next.equals("HOLE")) { NessusParser parser = NessusParser.getInstance(); PortValues portvals = null; DescrValues descrvals = null; int pluginId = -1, pluginSubId = -1; String pluginLogmsg = ""; String hostname = tokens[i++]; String portString = tokens[i++]; try { // Parse the service, port, and protocol of the hole portvals = parser.parsePort(portString); } catch (IllegalArgumentException ex) { log.error("Could not parse the port and protocol information out of this string: " + portString); portvals = NessusParser.getDefaultPortValues(); } String descrString = tokens[i++]; try { // Parse the descr of the event descrvals = parser.parseDescr(descrString); } catch (IllegalArgumentException ex) { log.error("Could not parse the severity, descr, and/or CVE entry information out of this string: \n" + descrString); descrvals = NessusParser.getDefaultDescrValues(); } String pluginIdString = tokens[i++]; try { pluginId = Integer.parseInt(pluginIdString); } catch (NumberFormatException ex) { log.error("Could not parse the plugin ID out of the string: " + pluginIdString, ex); } /* * Change this, once we get a way to break the * plugins down into separate vulnerabilities. */ pluginSubId = 0; final DBUtils d = new DBUtils(getClass()); try { conn = DataSourceFactory.getInstance().getConnection(); d.watch(conn); } catch (SQLException ex) { log.error("Could not open DB connection", ex); return SCAN_FATAL_ERROR; } try { PreparedStatement stmt = conn.prepareStatement(SELECT_OPEN_VULNERABILITY); d.watch(stmt); // ipaddr stmt.setString(1, InetAddressUtils.str(config.targetAddress)); // port if (portvals.port > 0) { stmt.setInt(2, portvals.port); } else { stmt.setNull(2, Types.INTEGER); } // protocol if (portvals.protocol != null) { stmt.setString(3, portvals.protocol); } else { stmt.setNull(2, Types.VARCHAR); } // pluginid and pluginsubid stmt.setInt(4, pluginId); stmt.setInt(5, pluginSubId); ResultSet openVuln = stmt.executeQuery(); d.watch(openVuln); // Update the timestamps on the existing events if (openVuln.next()) { stmt = conn.prepareStatement(VULNERABILITY_SCANNED); Timestamp currentTime = new Timestamp(new java.util.Date().getTime()); stmt.setTimestamp(1, currentTime); stmt.setTimestamp(2, currentTime); stmt.setInt(3, openVuln.getInt("vulnerabilityid")); int rowCount = stmt.executeUpdate(); if (rowCount != 1) { log.error("UNEXPECTED CONDITION: " + rowCount + " row(s) updated during last scan successful UPDATE call"); } else { openVulnerabilities.remove(new Integer(openVuln.getInt("vulnerabilityid"))); } if (openVuln.next()) { log.error("UNEXPECTED CONDITION: There are multiple rows that match this vulnerability, ignoring subsequent rows."); } } // Insert a new vulnerability row into the database else { stmt = conn.prepareStatement(SELECT_NEXT_ID); d.watch(stmt); ResultSet idRS = stmt.executeQuery(); d.watch(idRS); idRS.next(); int vulnId = idRS.getInt(1); idRS.close(); idRS = null; stmt = conn.prepareStatement(INSERT_NEW_VULNERABILITY); d.watch(stmt); stmt.setInt(1, vulnId); // Match the interface to a node in the database int nodeId = VulnscandConfigFactory.getInterfaceDbNodeId(conn, config.targetAddress); if (nodeId > 0) { stmt.setInt(2, nodeId); } else { stmt.setNull(2, Types.INTEGER); } stmt.setString(3, InetAddressUtils.str(config.targetAddress)); // ADD SERVICE CORRELATION // Punt this for now also... not necessary // stmt.setInt(4, serviceId); stmt.setNull(4, Types.INTEGER); Timestamp currentTime = new Timestamp(new java.util.Date().getTime()); stmt.setTimestamp(5, currentTime); stmt.setTimestamp(6, currentTime); stmt.setTimestamp(7, currentTime); stmt.setInt(8, descrvals.severity); stmt.setInt(9, pluginId); stmt.setInt(10, pluginSubId); PreparedStatement pluginStmt = conn.prepareStatement(SELECT_PLUGIN_INFO); d.watch(pluginStmt); pluginStmt.setInt(1, pluginId); pluginStmt.setInt(2, pluginSubId); ResultSet plugRS = pluginStmt.executeQuery(); d.watch(plugRS); if (plugRS.next()) { if (plugRS.getString("name") != null && plugRS.getString("name").length() > 0) { pluginLogmsg = plugRS.getString("name"); } if (plugRS.getString("summary") != null && plugRS.getString("summary").length() > 0) { if (!pluginLogmsg.equals("")) { pluginLogmsg += ": "; } pluginLogmsg += plugRS.getString("summary"); } } if (pluginLogmsg.equals("")) { /* * XXX Add a method that will query the Nessus * XXX server for information directly if it * XXX cannot be located in the database. */ // Punt this for now; we will pre-populate the DB if (portvals.port >= 0) { pluginLogmsg = "A vulnerability was detected on port " + portvals.port + ". See the description for more information."; } else { pluginLogmsg = "A vulnerability was detected. See the description for " + "more information."; } } stmt.setString(11, pluginLogmsg); stmt.setString(12, descrvals.descr); if (portvals.port >= 0) { stmt.setInt(13, portvals.port); } else { stmt.setNull(13, Types.INTEGER); } stmt.setString(14, portvals.protocol); if (descrvals.cveEntry != null) { stmt.setString(15, descrvals.cveEntry); } else { stmt.setNull(15, Types.VARCHAR); } if (stmt.executeUpdate() < 1) { log.error("UNEXPECTED CONDITION: No rows inserted during last INSERT call."); } } } catch (SQLException ex) { log.error("Error when querying database after " + next + " was found"); log.error(ex.getLocalizedMessage(), ex); return SCAN_FATAL_ERROR; } finally { d.cleanUp(); } return SCAN_SUCCESS; } // Indicates that a port/protocol is open else if (next.equals("PORT")) { NessusParser parser = NessusParser.getInstance(); PortValues portvals = null; int pluginId = -1, pluginSubId = -1; String hostname = tokens[i++]; String portString = tokens[i++]; try { // Parse the service, port, and protocol of the hole portvals = parser.parsePort(portString); } catch (IllegalArgumentException ex) { log.error("Could not parse the port and protocol information out of this string: " + portString); portvals = NessusParser.getDefaultPortValues(); } if (portvals.port < 0) { log.error("Port could not be determined from Nessus PORT message (" + portvals.port + "), dropping the message."); return SCAN_NON_FATAL_ERROR; } final DBUtils d = new DBUtils(getClass()); try { conn = DataSourceFactory.getInstance().getConnection(); d.watch(conn); } catch (SQLException ex) { log.error("Could not open DB connection", ex); return SCAN_FATAL_ERROR; } try { PreparedStatement stmt = conn.prepareStatement(SELECT_OPEN_VULNERABILITY); d.watch(stmt); // ipaddr stmt.setString(1, InetAddressUtils.str(config.targetAddress)); // port if (portvals.port > 0) { stmt.setInt(2, portvals.port); } else { stmt.setNull(2, Types.INTEGER); } // protocol if (portvals.protocol != null) { stmt.setString(3, portvals.protocol); } else { stmt.setNull(2, Types.VARCHAR); } // pluginid and pluginsubid stmt.setInt(4, PORTSCAN_PLUGIN_ID); stmt.setInt(5, 0); ResultSet openVuln = stmt.executeQuery(); d.watch(openVuln); // Update the timestamps on the existing events if (openVuln.next()) { stmt = conn.prepareStatement(VULNERABILITY_SCANNED); d.watch(stmt); Timestamp currentTime = new Timestamp(new java.util.Date().getTime()); stmt.setTimestamp(1, currentTime); stmt.setTimestamp(2, currentTime); stmt.setInt(3, openVuln.getInt("vulnerabilityid")); int rowCount = stmt.executeUpdate(); if (rowCount != 1) { log.error("UNEXPECTED CONDITION: " + rowCount + " row(s) updated during last scan successful UPDATE call"); } else { openVulnerabilities.remove(new Integer(openVuln.getInt("vulnerabilityid"))); } if (openVuln.next()) { log.error("UNEXPECTED CONDITION: There are multiple rows that match this vulnerability, ignoring subsequent rows."); } } // Insert a new vulnerability row into the database else { stmt = conn.prepareStatement(SELECT_NEXT_ID); d.watch(stmt); ResultSet idRS = stmt.executeQuery(); d.watch(idRS); idRS.next(); int vulnId = idRS.getInt(1); stmt = conn.prepareStatement(INSERT_NEW_VULNERABILITY); d.watch(stmt); stmt.setInt(1, vulnId); // Match the interface to a node in the database int nodeId = VulnscandConfigFactory.getInterfaceDbNodeId(conn, config.targetAddress); if (nodeId > 0) { stmt.setInt(2, nodeId); } else { stmt.setNull(2, Types.INTEGER); } stmt.setString(3, InetAddressUtils.str(config.targetAddress)); // ADD SERVICE CORRELATION // Punt this for now also... not necessary // stmt.setInt(4, serviceId); stmt.setNull(4, Types.INTEGER); Timestamp currentTime = new Timestamp(new java.util.Date().getTime()); stmt.setTimestamp(5, currentTime); stmt.setTimestamp(6, currentTime); stmt.setTimestamp(7, currentTime); // Use Normal severity for open ports stmt.setInt(8, OnmsSeverity.NORMAL.getId()); stmt.setInt(9, PORTSCAN_PLUGIN_ID); stmt.setInt(10, 0); stmt.setString(11, "Port " + portvals.port + " is open on this host."); stmt.setString(12, "Port " + portvals.port + " is open on this host."); if (portvals.port >= 0) { stmt.setInt(13, portvals.port); } else { stmt.setNull(13, Types.INTEGER); } // Protocol stmt.setString(14, portvals.protocol); // CVE entry stmt.setNull(15, Types.VARCHAR); if (stmt.executeUpdate() < 1) { log.error("UNEXPECTED CONDITION: No rows inserted during last INSERT call."); } } } catch (SQLException ex) { log.error("Error when querying database after " + next + " was found"); log.error(ex.getLocalizedMessage(), ex); return SCAN_FATAL_ERROR; } finally { d.cleanUp(); } return SCAN_SUCCESS; } else if (next.equals("STATUS")) { // Shouldn't get any of these log.error("Weird... a non-abbreviated STATUS message. Check your code."); return SCAN_NON_FATAL_ERROR; } else if (next.equals("BYE")) { log.debug("BYE message received, ending scan"); if (lastPlugin == totalPlugins) { // If the scan completed running each plugin return SCAN_COMPLETE; } else { // Otherwise, do not resolve undetected plugins return SCAN_FATAL_ERROR; } } else { log.warn("Unhandled message type from Nessus: " + next + "\n" + message); return SCAN_NON_FATAL_ERROR; } } else if (message.startsWith("s:")) { // Abbreviated status messages message = message.substring("s:".length()).trim(); StringTokenizer parts = new StringTokenizer(message, ":"); String type, hostname; int last, total; try { String next = parts.nextToken(); if (next.equals("p")) { type = "portscan"; // Ignore the parameters for portscans, // always report SCAN_SUCCESS return SCAN_SUCCESS; } else if (next.equals("a")) { type = "attack"; hostname = parts.nextToken(); last = Integer.parseInt(parts.nextToken()); total = Integer.parseInt(parts.nextToken()); if (lastPlugin >= 0) { /* * If the plugin increment magically * goes down because Nessus is * starting another unwanted scan, * report the scan complete so it * will terminate the connection. */ if (last < lastPlugin) { log.warn("UNEXPECTED CONDITION: The completed plugin counter decreased. Reporting the current scan complete."); return SCAN_COMPLETE; } } lastPlugin = last; log.debug("Last plugin: " + lastPlugin); // Set the plugin total if (totalPlugins <= 0) { totalPlugins = total; log.debug("Plugin total: " + totalPlugins); } return SCAN_SUCCESS; } else { log.error("UNEXPECTED CONDITION: Invalid abbreviated status message from Nessus, discarding... \n\t" + message); return SCAN_NON_FATAL_ERROR; } } catch (NoSuchElementException ex) { log.error("UNEXPECTED CONDITION: Invalid abbreviated status message from Nessus, discarding... \n\t" + message); return SCAN_FATAL_ERROR; } catch (NumberFormatException ex) { log.error("UNEXPECTED CONDITION: Could not parse integers out of this Nessus status message: " + message); return SCAN_FATAL_ERROR; } } else { log.warn("UNEXPECTED CONDITION: Unhandled message from Nessus: " + message); return SCAN_NON_FATAL_ERROR; } } /** * <p>readLines</p> * * @param in a {@link java.io.InputStream} object. * @return a {@link org.opennms.core.queue.FifoQueue} object. */ public FifoQueue<String> readLines(InputStream in) { ThreadCategory log = ThreadCategory.getInstance(getClass()); String EOL = "\n"; String alreadyRecdData = null; FifoQueue<String> retval = new FifoQueueImpl<String>(); ByteArrayOutputStream xmlStr = new ByteArrayOutputStream(); int bytesInThisRead = 0; // loop until we've read it all or we have to shutdown while (true) { // read data off the socket's input stream try { byte[] message = new byte[1024]; bytesInThisRead = in.read(message); if (log.isDebugEnabled()) { log.debug("bytesInThisRead: " + bytesInThisRead); } /* * Check the result code. A negative value * means that the end of file has been reached * Otherwise the value must be greater than zero * according to the Java API documentation */ if (bytesInThisRead < 0) { break; } /* * Check if current chunk of data has end of data * care should be exercised since the buffer may contain * more than one log message. */ String newData = new String(message, 0, bytesInThisRead); String tempStr; if (alreadyRecdData != null) { tempStr = alreadyRecdData + newData; } else { tempStr = newData; } int index = -1; while ((index = tempStr.indexOf(EOL)) != -1) { int tlen = index + EOL.length(); if (tlen > tempStr.length()) { tlen = tempStr.length(); } byte[] tempb = tempStr.substring(0, tlen).getBytes(); xmlStr.write(tempb, 0, tlen); // Create a new text message retval.add(new String(xmlStr.toByteArray(), 0, xmlStr.size())); xmlStr.reset(); alreadyRecdData = null; if (tlen != tempStr.length()) { tempStr = tempStr.substring(tlen); } else if (tlen == tempStr.length()) { tempStr = ""; } } if (tempStr.length() != 0) { alreadyRecdData = tempStr; } if (bytesInThisRead < 1024) { /* * Return any remaining data as the last line * in the queue. */ if ((alreadyRecdData != null) && (alreadyRecdData.length() != 0)) { retval.add(alreadyRecdData); } break; } } catch (FifoQueueException ex) { log.warn(ex.getMessage()); } catch (InterruptedException ex) { log.warn(ex.getMessage()); } catch (IOException e) { log.warn(e.getMessage()); } } return retval; } }