/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2008-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.config;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.InetAddress;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.commons.io.IOUtils;
import org.opennms.core.utils.ByteArrayComparator;
import org.opennms.core.utils.ConfigFileConstants;
import org.opennms.core.utils.IPLike;
import org.opennms.core.utils.InetAddressUtils;
import org.opennms.core.utils.LogUtils;
import org.opennms.core.xml.JaxbUtils;
import org.opennms.netmgt.config.snmp.Definition;
import org.opennms.netmgt.config.snmp.Range;
import org.opennms.netmgt.config.snmp.SnmpConfig;
import org.opennms.netmgt.snmp.SnmpAgentConfig;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.xml.sax.InputSource;
/**
* This class is the main repository for SNMP configuration information used by
* the capabilities daemon. When this class is loaded it reads the snmp
* configuration into memory, and uses the configuration to find the
* {@link org.opennms.netmgt.snmp.SnmpAgentConfig SnmpAgentConfig} objects for specific
* addresses. If an address cannot be located in the configuration then a
* default peer instance is returned to the caller.
*
* <strong>Note: </strong>Users of this class should make sure the
* <em>init()</em> is called before calling any other method to ensure the
* config is loaded before accessing other convenience methods.
*
* @author <a href="mailto:david@opennms.org">David Hustace</a>
* @author <a href="mailto:weave@oculan.com">Weave</a>
* @author <a href="mailto:gturner@newedgenetworks.com">Gerald Turner</a>
*/
public class SnmpPeerFactory implements SnmpAgentConfigFactory {
private static final int DEFAULT_SNMP_PORT = 161;
private static final ReadWriteLock m_globalLock = new ReentrantReadWriteLock();
private static final Lock m_readLock = m_globalLock.readLock();
private static final Lock m_writeLock = m_globalLock.writeLock();
/**
* The singleton instance of this factory
*/
private static SnmpPeerFactory m_singleton = null;
/**
* The config class loaded from the config file
*/
private static SnmpConfig m_config;
private static File m_configFile;
/**
* This member is set to true if the configuration file has been loaded.
*/
private static boolean m_loaded = false;
private static final int VERSION_UNSPECIFIED = -1;
/**
* Private constructor
*
* @exception java.io.IOException
* Thrown if the specified config file cannot be read
* @exception org.exolab.castor.xml.MarshalException
* Thrown if the file does not conform to the schema.
* @exception org.exolab.castor.xml.ValidationException
* Thrown if the contents do not match the required schema.
*/
private SnmpPeerFactory(final File configFile) throws IOException {
this(new FileSystemResource(configFile));
}
/**
* <p>Constructor for SnmpPeerFactory.</p>
*
* @param resource a {@link org.springframework.core.io.Resource} object.
*/
public SnmpPeerFactory(final Resource resource) {
SnmpPeerFactory.getWriteLock().lock();
try {
m_config = JaxbUtils.unmarshal(SnmpConfig.class, resource);
} finally {
SnmpPeerFactory.getWriteLock().unlock();
}
}
/**
* <p>Constructor for SnmpPeerFactory.</p>
*
* @param rdr a {@link java.io.Reader} object.
* @throws java.io.IOException if any.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
* @deprecated Use code for InputStream instead to avoid character set issues
*/
public SnmpPeerFactory(final Reader rdr) throws IOException {
SnmpPeerFactory.getWriteLock().lock();
try {
m_config = JaxbUtils.unmarshal(SnmpConfig.class, rdr);
} finally {
SnmpPeerFactory.getWriteLock().unlock();
}
}
/**
* A constructor that takes a config string for use mostly in tests
*/
public SnmpPeerFactory(final String configString) throws IOException {
SnmpPeerFactory.getWriteLock().lock();
try {
m_config = JaxbUtils.unmarshal(SnmpConfig.class, configString);
} finally {
SnmpPeerFactory.getWriteLock().unlock();
}
}
/**
* <p>Constructor for SnmpPeerFactory.</p>
*
* @param stream a {@link java.io.InputStream} object.
*/
public SnmpPeerFactory(final InputStream stream) {
SnmpPeerFactory.getWriteLock().lock();
try {
m_config = JaxbUtils.unmarshal(SnmpConfig.class, new InputSource(stream), null);
} finally {
SnmpPeerFactory.getWriteLock().unlock();
}
}
public static Lock getReadLock() {
return m_readLock;
}
public static Lock getWriteLock() {
return m_writeLock;
}
/**
* Load the config from the default config file and create the singleton
* instance of this factory.
*
* @exception java.io.IOException
* Thrown if the specified config file cannot be read
* @exception org.exolab.castor.xml.MarshalException
* Thrown if the file does not conform to the schema.
* @exception org.exolab.castor.xml.ValidationException
* Thrown if the contents do not match the required schema.
* @throws java.io.IOException if any.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
*/
public static void init() throws IOException {
SnmpPeerFactory.getWriteLock().lock();
try {
if (m_loaded) {
// init already called - return
// to reload, reload() will need to be called
return;
}
final File cfgFile = getFile();
LogUtils.debugf(SnmpPeerFactory.class, "init: config file path: %s", cfgFile.getPath());
m_singleton = new SnmpPeerFactory(cfgFile);
m_loaded = true;
} finally {
SnmpPeerFactory.getWriteLock().unlock();
}
}
/**
* Saves the current settings to disk
*
* @throws java.io.IOException if any.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
*/
public static void saveCurrent() throws IOException {
saveToFile(getFile());
}
public static void saveToFile(final File file)
throws UnsupportedEncodingException, FileNotFoundException,
IOException {
// Marshal to a string first, then write the string to the file. This
// way the original config
// isn't lost if the XML from the marshal is hosed.
final String marshalledConfig = marshallConfig();
SnmpPeerFactory.getWriteLock().lock();
FileOutputStream out = null;
Writer fileWriter = null;
try {
if (marshalledConfig != null) {
out = new FileOutputStream(file);
fileWriter = new OutputStreamWriter(out, "UTF-8");
fileWriter.write(marshalledConfig);
fileWriter.flush();
fileWriter.close();
}
} finally {
IOUtils.closeQuietly(fileWriter);
IOUtils.closeQuietly(out);
SnmpPeerFactory.getWriteLock().unlock();
}
}
/**
* Return the singleton instance of this factory.
*
* @return The current factory instance.
* @throws java.lang.IllegalStateException
* Thrown if the factory has not yet been initialized.
*/
public static SnmpPeerFactory getInstance() {
SnmpPeerFactory.getReadLock().lock();
try {
if (!m_loaded) {
throw new IllegalStateException("The factory has not been initialized");
}
return m_singleton;
} finally {
SnmpPeerFactory.getReadLock().unlock();
}
}
/**
* <p>setFile</p>
*
* @param configFile a {@link java.io.File} object.
*/
public static void setFile(final File configFile) {
SnmpPeerFactory.getWriteLock().lock();
try {
final File oldFile = m_configFile;
m_configFile = configFile;
// if the file changed then we need to reload the config
if (oldFile == null || m_configFile == null || !oldFile.equals(m_configFile)) {
m_singleton = null;
m_loaded = false;
}
} finally {
SnmpPeerFactory.getWriteLock().unlock();
}
}
/**
* <p>getFile</p>
*
* @return a {@link java.io.File} object.
* @throws java.io.IOException if any.
*/
public static File getFile() throws IOException {
SnmpPeerFactory.getReadLock().lock();
try {
if (m_configFile == null) {
setFile(ConfigFileConstants.getFile(ConfigFileConstants.SNMP_CONF_FILE_NAME));
}
return m_configFile;
} finally {
SnmpPeerFactory.getReadLock().unlock();
}
}
/**
* <p>setInstance</p>
*
* @param singleton a {@link org.opennms.netmgt.config.SnmpPeerFactory} object.
*/
public static void setInstance(final SnmpPeerFactory singleton) {
SnmpPeerFactory.getWriteLock().lock();
try {
m_singleton = singleton;
m_loaded = true;
} finally {
SnmpPeerFactory.getWriteLock().unlock();
}
}
/** {@inheritDoc} */
public SnmpAgentConfig getAgentConfig(final InetAddress agentAddress) {
return getAgentConfig(agentAddress, VERSION_UNSPECIFIED);
}
private SnmpAgentConfig getAgentConfig(final InetAddress agentInetAddress, final int requestedSnmpVersion) {
SnmpPeerFactory.getReadLock().lock();
try {
if (m_config == null) {
final SnmpAgentConfig agentConfig = new SnmpAgentConfig(agentInetAddress);
if (requestedSnmpVersion == VERSION_UNSPECIFIED) {
agentConfig.setVersion(SnmpAgentConfig.DEFAULT_VERSION);
} else {
agentConfig.setVersion(requestedSnmpVersion);
}
return agentConfig;
}
final SnmpAgentConfig agentConfig = new SnmpAgentConfig(agentInetAddress);
// Now set the defaults from the m_config
setSnmpAgentConfig(agentConfig, new Definition(), requestedSnmpVersion);
// Attempt to locate the node
DEFLOOP: for (final Definition def : m_config.getDefinitionCollection()) {
// check the specifics first
for (final String saddr : def.getSpecificCollection()) {
try {
final InetAddress addr = InetAddressUtils.addr(saddr);
if (addr != null && addr.equals(agentConfig.getAddress())) {
setSnmpAgentConfig(agentConfig, def, requestedSnmpVersion);
break DEFLOOP;
}
} catch (final IllegalArgumentException e) {
LogUtils.debugf(this, e, "Error while reading SNMP config <specific> tag: %s", saddr);
}
}
// check the ranges
//
final ByteArrayComparator comparator = new ByteArrayComparator();
for (final Range rng : def.getRangeCollection()) {
final byte[] addr = agentConfig.getAddress().getAddress();
final byte[] begin = InetAddressUtils.toIpAddrBytes(rng.getBegin());
final byte[] end = InetAddressUtils.toIpAddrBytes(rng.getEnd());
boolean inRange = InetAddressUtils.isInetAddressInRange(addr, begin, end);
if (comparator.compare(begin, end) <= 0) {
inRange = InetAddressUtils.isInetAddressInRange(addr, begin, end);
} else {
LogUtils.warnf(this, "%s has an 'end' that is earlier than its 'beginning'!", rng);
inRange = InetAddressUtils.isInetAddressInRange(addr, end, begin);
}
if (inRange) {
setSnmpAgentConfig(agentConfig, def, requestedSnmpVersion);
break DEFLOOP;
}
}
// check the matching ip expressions
for (final String ipMatch : def.getIpMatchCollection()) {
if (IPLike.matches(agentInetAddress, ipMatch)) {
setSnmpAgentConfig(agentConfig, def, requestedSnmpVersion);
break DEFLOOP;
}
}
} // end DEFLOOP
if (agentConfig == null) {
final Definition def = new Definition();
setSnmpAgentConfig(agentConfig, def, requestedSnmpVersion);
}
return agentConfig;
} finally {
SnmpPeerFactory.getReadLock().unlock();
}
}
private void setSnmpAgentConfig(final SnmpAgentConfig agentConfig, final Definition def, final int requestedSnmpVersion) {
int version = determineVersion(def, requestedSnmpVersion);
setCommonAttributes(agentConfig, def, version);
agentConfig.setSecurityLevel(determineSecurityLevel(def));
agentConfig.setSecurityName(determineSecurityName(def));
agentConfig.setAuthProtocol(determineAuthProtocol(def));
agentConfig.setAuthPassPhrase(determineAuthPassPhrase(def));
agentConfig.setPrivPassPhrase(determinePrivPassPhrase(def));
agentConfig.setPrivProtocol(determinePrivProtocol(def));
agentConfig.setReadCommunity(determineReadCommunity(def));
agentConfig.setWriteCommunity(determineWriteCommunity(def));
}
/**
* This is a helper method to set all the common attributes in the agentConfig.
*
* @param agentConfig
* @param def
* @param version
*/
private void setCommonAttributes(final SnmpAgentConfig agentConfig, final Definition def, final int version) {
agentConfig.setVersion(version);
agentConfig.setPort(determinePort(def));
agentConfig.setRetries(determineRetries(def));
agentConfig.setTimeout((int)determineTimeout(def));
agentConfig.setMaxRequestSize(determineMaxRequestSize(def));
agentConfig.setMaxVarsPerPdu(determineMaxVarsPerPdu(def));
agentConfig.setMaxRepetitions(determineMaxRepetitions(def));
InetAddress proxyHost = determineProxyHost(def);
if (proxyHost != null) {
agentConfig.setProxyFor(agentConfig.getAddress());
agentConfig.setAddress(determineProxyHost(def));
}
}
private int determineMaxRepetitions(final Definition def) {
return (!def.hasMaxRepetitions() ?
(!m_config.hasMaxRepetitions() ?
SnmpAgentConfig.DEFAULT_MAX_REPETITIONS : m_config.getMaxRepetitions()) : def.getMaxRepetitions());
}
private InetAddress determineProxyHost(final Definition def) {
InetAddress inetAddr = null;
final String address = def.getProxyHost() == null ? (m_config.getProxyHost() == null ? null : m_config.getProxyHost()) : def.getProxyHost();
if (address != null) {
try {
inetAddr = InetAddressUtils.addr(address);
} catch (final IllegalArgumentException e) {
LogUtils.debugf(this, e, "Error while reading SNMP config proxy host: %s", address);
}
}
return inetAddr;
}
private int determineMaxVarsPerPdu(final Definition def) {
return (!def.hasMaxVarsPerPdu() ?
(!m_config.hasMaxVarsPerPdu() ?
SnmpAgentConfig.DEFAULT_MAX_VARS_PER_PDU : m_config.getMaxVarsPerPdu()) : def.getMaxVarsPerPdu());
}
/**
* Helper method to search the snmp-config for the appropriate read
* community string.
* @param def
* @return
*/
private String determineReadCommunity(final Definition def) {
return (def.getReadCommunity() == null ? (m_config.getReadCommunity() == null ? SnmpAgentConfig.DEFAULT_READ_COMMUNITY :m_config.getReadCommunity()) : def.getReadCommunity());
}
/**
* Helper method to search the snmp-config for the appropriate write
* community string.
* @param def
* @return
*/
private String determineWriteCommunity(final Definition def) {
return (def.getWriteCommunity() == null ? (m_config.getWriteCommunity() == null ? SnmpAgentConfig.DEFAULT_WRITE_COMMUNITY :m_config.getWriteCommunity()) : def.getWriteCommunity());
}
/**
* Helper method to search the snmp-config for the appropriate maximum
* request size. The default is the minimum necessary for a request.
* @param def
* @return
*/
private int determineMaxRequestSize(final Definition def) {
return (!def.hasMaxRequestSize() ? (!m_config.hasMaxRequestSize() ? SnmpAgentConfig.DEFAULT_MAX_REQUEST_SIZE : m_config.getMaxRequestSize()) : def.getMaxRequestSize());
}
/**
* Helper method to find a security name to use in the snmp-config. If v3 has
* been specified and one can't be found, then a default is used for this
* is a required option for v3 operations.
* @param def
* @return
*/
private String determineSecurityName(final Definition def) {
final String securityName = (def.getSecurityName() == null ? m_config.getSecurityName() : def.getSecurityName() );
if (securityName == null) {
return SnmpAgentConfig.DEFAULT_SECURITY_NAME;
}
return securityName;
}
/**
* Helper method to find a security name to use in the snmp-config. If v3 has
* been specified and one can't be found, then a default is used for this
* is a required option for v3 operations.
* @param def
* @return
*/
private String determineAuthProtocol(final Definition def) {
final String authProtocol = (def.getAuthProtocol() == null ? m_config.getAuthProtocol() : def.getAuthProtocol());
if (authProtocol == null) {
return SnmpAgentConfig.DEFAULT_AUTH_PROTOCOL;
}
return authProtocol;
}
/**
* Helper method to find a authentication passphrase to use from the snmp-config. If v3 has
* been specified and one can't be found, then a default is used for this
* is a required option for v3 operations.
* @param def
* @return
*/
private String determineAuthPassPhrase(final Definition def) {
final String authPassPhrase = (def.getAuthPassphrase() == null ? m_config.getAuthPassphrase() : def.getAuthPassphrase());
if (authPassPhrase == null) {
return SnmpAgentConfig.DEFAULT_AUTH_PASS_PHRASE;
}
return authPassPhrase;
}
/**
* Helper method to find a privacy passphrase to use from the snmp-config. If v3 has
* been specified and one can't be found, then a default is used for this
* is a required option for v3 operations.
* @param def
* @return
*/
private String determinePrivPassPhrase(final Definition def) {
final String privPassPhrase = (def.getPrivacyPassphrase() == null ? m_config.getPrivacyPassphrase() : def.getPrivacyPassphrase());
if (privPassPhrase == null) {
return SnmpAgentConfig.DEFAULT_PRIV_PASS_PHRASE;
}
return privPassPhrase;
}
/**
* Helper method to find a privacy protocol to use from the snmp-config. If v3 has
* been specified and one can't be found, then a default is used for this
* is a required option for v3 operations.
* @param def
* @return
*/
private String determinePrivProtocol(final Definition def) {
final String authPrivProtocol = (def.getPrivacyProtocol() == null ? m_config.getPrivacyProtocol() : def.getPrivacyProtocol());
if (authPrivProtocol == null) {
return SnmpAgentConfig.DEFAULT_PRIV_PROTOCOL;
}
return authPrivProtocol;
}
/**
* Helper method to set the security level in v3 operations. The default is
* noAuthNoPriv if there is no authentication passphrase. From there, if
* there is a privacy passphrase supplied, then the security level is set to
* authPriv else it falls out to authNoPriv. There are only these 3 possible
* security levels.
* default
* @param def
* @return
*/
private int determineSecurityLevel(final Definition def) {
// use the def security level first
if (def.hasSecurityLevel()) {
return def.getSecurityLevel();
}
// use a configured default security level next
if (m_config.hasSecurityLevel()) {
return m_config.getSecurityLevel();
}
// if no security level configuration exists use
int securityLevel = SnmpAgentConfig.NOAUTH_NOPRIV;
final String authPassPhrase = (def.getAuthPassphrase() == null ? m_config.getAuthPassphrase() : def.getAuthPassphrase());
final String privPassPhrase = (def.getPrivacyPassphrase() == null ? m_config.getPrivacyPassphrase() : def.getPrivacyPassphrase());
if (authPassPhrase == null) {
securityLevel = SnmpAgentConfig.NOAUTH_NOPRIV;
} else {
if (privPassPhrase == null) {
securityLevel = SnmpAgentConfig.AUTH_NOPRIV;
} else {
securityLevel = SnmpAgentConfig.AUTH_PRIV;
}
}
return securityLevel;
}
/**
* Helper method to search the snmp-config for a port
* @param def
* @return
*/
private int determinePort(final Definition def) {
return (def.getPort() == 0 ? (m_config.getPort() == 0 ? DEFAULT_SNMP_PORT : m_config.getPort()) : def.getPort());
}
/**
* Helper method to search the snmp-config
* @param def
* @return
*/
private long determineTimeout(final Definition def) {
final long timeout = SnmpAgentConfig.DEFAULT_TIMEOUT;
return (def.getTimeout() == 0 ? (m_config.getTimeout() == 0 ? timeout : m_config.getTimeout()) : def.getTimeout());
}
private int determineRetries(final Definition def) {
final int retries = SnmpAgentConfig.DEFAULT_RETRIES;
return (def.getRetry() == 0 ? (m_config.getRetry() == 0 ? retries : m_config.getRetry()) : def.getRetry());
}
/**
* This method determines the configured SNMP version.
* the order of operations is:
* 1st: return a valid requested version
* 2nd: return a valid version defined in a definition within the snmp-config
* 3rd: return a valid version in the snmp-config
* 4th: return the default version
*
* @param def
* @param requestedSnmpVersion
* @return
*/
private int determineVersion(final Definition def, final int requestedSnmpVersion) {
int version = SnmpAgentConfig.VERSION1;
String cfgVersion = "v1";
if (requestedSnmpVersion == VERSION_UNSPECIFIED) {
if (def.getVersion() == null) {
if (m_config.getVersion() == null) {
return version;
} else {
cfgVersion = m_config.getVersion();
}
} else {
cfgVersion = def.getVersion();
}
} else {
return requestedSnmpVersion;
}
if (cfgVersion.equals("v1")) {
version = SnmpAgentConfig.VERSION1;
} else if (cfgVersion.equals("v2c")) {
version = SnmpAgentConfig.VERSION2C;
} else if (cfgVersion.equals("v3")) {
version = SnmpAgentConfig.VERSION3;
}
return version;
}
/**
* <p>getSnmpConfig</p>
*
* @return a {@link org.opennms.netmgt.config.snmp.SnmpConfig} object.
*/
public static SnmpConfig getSnmpConfig() {
SnmpPeerFactory.getReadLock().lock();
try {
return m_config;
} finally {
SnmpPeerFactory.getReadLock().unlock();
}
}
/**
* Enhancement: Allows specific or ranges to be merged into SNMP configuration
* with many other attributes. Uses new classes the wrap Castor-generated code to
* help with merging, comparing, and optimizing definitions. Thanks for your
* initial work on this Gerald.
*
* Puts a specific IP address with associated read-community string into
* the currently loaded snmp-config.xml.
*
* @param info a {@link org.opennms.netmgt.config.SnmpEventInfo} object.
*/
public void define(final SnmpEventInfo info) {
getWriteLock().lock();
try {
final SnmpConfigManager mgr = new SnmpConfigManager(m_config);
mgr.mergeIntoConfig(info.createDef());
} finally {
getWriteLock().unlock();
}
}
/**
* Creates a string containing the XML of the current SnmpConfig
*
* @return Marshalled SnmpConfig
*/
public static String marshallConfig() {
SnmpPeerFactory.getReadLock().lock();
try {
String marshalledConfig = null;
StringWriter writer = null;
try {
writer = new StringWriter();
JaxbUtils.marshal(m_config, writer);
marshalledConfig = writer.toString();
} finally {
IOUtils.closeQuietly(writer);
}
return marshalledConfig;
} finally {
SnmpPeerFactory.getReadLock().unlock();
}
}
}