/*
* RHQ Management Platform
* Copyright (C) 2005-2013 Red Hat, Inc.
* All rights reserved.
*
* 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 version 2 of the License.
*
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.enterprise.server.plugins.alertSnmp;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.snmp4j.CommandResponderEvent;
import org.snmp4j.CommunityTarget;
import org.snmp4j.MessageException;
import org.snmp4j.PDU;
import org.snmp4j.PDUv1;
import org.snmp4j.ScopedPDU;
import org.snmp4j.Snmp;
import org.snmp4j.Target;
import org.snmp4j.UserTarget;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.CounterSupport;
import org.snmp4j.mp.DefaultCounterListener;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.mp.StateReference;
import org.snmp4j.mp.StatusInformation;
import org.snmp4j.security.AuthMD5;
import org.snmp4j.security.AuthSHA;
import org.snmp4j.security.PrivAES128;
import org.snmp4j.security.PrivAES192;
import org.snmp4j.security.PrivAES256;
import org.snmp4j.security.PrivDES;
import org.snmp4j.security.SecurityLevel;
import org.snmp4j.security.SecurityModels;
import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.security.USM;
import org.snmp4j.security.UsmUser;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.IpAddress;
import org.snmp4j.smi.Null;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.TcpAddress;
import org.snmp4j.smi.TimeTicks;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.smi.UnsignedInteger32;
import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.AbstractTransportMapping;
import org.snmp4j.transport.DefaultTcpTransportMapping;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.PDUFactory;
import org.rhq.core.domain.alert.Alert;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.util.StringUtil;
/**
* @author Ian Springer
* @author Heiko W. Rupp
*/
@SuppressWarnings("unused")
public class SnmpTrapSender implements PDUFactory {
public static final int DEFAULT = 0;
private static final String UDP_TRANSPORT = "udp";
private static final String TCP_TRANSPORT = "tcp";
private static final String DEFAULT_RHQ_BINDING = "1.3.6.1.4.1.18016.2.1";
private Log log = LogFactory.getLog(SnmpTrapSender.class);
private Target target;
private OID authProtocol;
private OID privProtocol;
private OctetString privPassphrase;
private OctetString authPassphrase;
private OctetString community = new OctetString("public");
private OctetString contextEngineID;
private OctetString contextName = new OctetString();
private OctetString securityName = new OctetString();
private TimeTicks sysUpTime = new TimeTicks(0);
public static final OID enterpriseSpecificTrap =
new OID(new int[] { 1,3,6,1,6,3,1,1,5,6 });
private OID trapOID = enterpriseSpecificTrap;
private PDUv1 v1TrapPDU = new PDUv1();
private int version = SnmpConstants.version3;
private int retries = 1;
private int timeout = 1000;
private int pduType = PDU.TRAP;
private int maxRepetitions = 10;
private int nonRepeaters = 0;
private List<VariableBinding> vbs = new ArrayList<VariableBinding>();
private Address address;
private int operation = DEFAULT;
private boolean snmpEnabled;
Configuration systemConfig;
public SnmpTrapSender(Configuration preferences) {
this.systemConfig = preferences;
this.snmpEnabled = init();
}
private void checkTrapVariables(List<VariableBinding> vbs) {
// Only on SNMP v2c or v3 trap
if ((pduType == PDU.INFORM) || (pduType == PDU.TRAP)) {
// Insert sysUpTime OID if not already present
if ((vbs.size() == 0) || ((vbs.size() >= 1) && (!(vbs.get(0)).getOid().equals(SnmpConstants.sysUpTime)))) {
vbs.add(0, new VariableBinding(SnmpConstants.sysUpTime, sysUpTime));
}
// Insert trap OID if not already present
if ((vbs.size() == 1) || ((vbs.size() >= 2) && (!(vbs.get(1)).getOid().equals(SnmpConstants.snmpTrapOID)))) {
vbs.add(1, new VariableBinding(SnmpConstants.snmpTrapOID, trapOID));
}
}
}
private void addUsmUser(Snmp snmp) {
snmp.getUSM().addUser(securityName,
new UsmUser(securityName, authProtocol, authPassphrase, privProtocol, privPassphrase));
}
private Snmp createSnmpSession() throws IOException {
AbstractTransportMapping transport;
if (address instanceof TcpAddress) {
transport = new DefaultTcpTransportMapping();
} else {
transport = new DefaultUdpTransportMapping();
}
// Could save some CPU cycles:
// transport.setAsyncMsgProcessingSupported(false);
Snmp snmp = new Snmp(transport);
if (version == SnmpConstants.version3) {
USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0);
SecurityModels.getInstance().addSecurityModel(usm);
addUsmUser(snmp);
}
return snmp;
}
private Target createTarget() {
if (version == SnmpConstants.version3) {
UserTarget target = new UserTarget();
if (authPassphrase != null) {
if (privPassphrase != null) {
target.setSecurityLevel(SecurityLevel.AUTH_PRIV);
} else {
target.setSecurityLevel(SecurityLevel.AUTH_NOPRIV);
}
} else {
target.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV);
}
target.setSecurityName(securityName);
return target;
} else {
CommunityTarget target = new CommunityTarget();
target.setCommunity(community);
return target;
}
}
public PDU send() throws IOException {
Snmp snmp = createSnmpSession();
try {
this.target = createTarget();
target.setVersion(version);
target.setAddress(address);
target.setRetries(retries);
target.setTimeout(timeout);
snmp.listen();
PDU request = createPDU(target);
if (request.getType() == PDU.GETBULK) {
request.setMaxRepetitions(maxRepetitions);
request.setNonRepeaters(nonRepeaters);
}
for (Object vb : vbs) {
request.add((VariableBinding) vb);
}
PDU response = null;
ResponseEvent responseEvent;
long startTime = System.currentTimeMillis();
responseEvent = snmp.send(request, target);
if (responseEvent != null) {
response = responseEvent.getResponse();
if (log.isDebugEnabled())
log.debug("Received response after " + (System.currentTimeMillis() - startTime) + " millis");
}
return response;
} finally {
snmp.close();
}
}
private void getVariableBindings(String args) {
String oid = args;
char type = 'i';
String value = null;
int equal = oid.indexOf("={");
if (equal > 0) {
oid = args.substring(0, equal);
type = args.charAt(equal + 2);
value = args.substring(args.indexOf('}') + 1);
} else if (oid.indexOf('-') > 0) {
StringTokenizer st = new StringTokenizer(oid, "-");
if (st.countTokens() != 2) {
throw new IllegalArgumentException("Illegal OID range specified: '" + oid);
}
oid = st.nextToken();
VariableBinding vbLower = new VariableBinding(new OID(oid));
vbs.add(vbLower);
long last = Long.parseLong(st.nextToken());
long first = vbLower.getOid().lastUnsigned();
for (long k = first + 1; k <= last; k++) {
OID nextOID = new OID(vbLower.getOid().getValue(), 0, vbLower.getOid().size() - 1);
nextOID.appendUnsigned(k);
VariableBinding next = new VariableBinding(nextOID);
vbs.add(next);
}
return;
}
VariableBinding vb = new VariableBinding(new OID(oid));
if (value != null) {
Variable variable;
switch (type) {
case 'i': {
variable = new Integer32(Integer.parseInt(value));
break;
}
case 'u': {
variable = new UnsignedInteger32(Long.parseLong(value));
break;
}
case 's': {
variable = new OctetString(value);
break;
}
case 'x': {
variable = OctetString.fromString(value, ':', 16);
break;
}
case 'd': {
variable = OctetString.fromString(value, '.', 10);
break;
}
case 'b': {
variable = OctetString.fromString(value, ' ', 2);
break;
}
case 'n': {
variable = new Null();
break;
}
case 'o': {
variable = new OID(value);
break;
}
case 't': {
variable = new TimeTicks(Long.parseLong(value));
break;
}
case 'a': {
variable = new IpAddress(value);
break;
}
default: {
throw new IllegalArgumentException("Variable type " + type + " not supported");
}
}
vb.setVariable(variable);
}
vbs.add(vb);
}
private static OctetString createOctetString(String s) {
OctetString octetString;
if (s.startsWith("0x")) {
octetString = OctetString.fromHexString(s.substring(2), ':');
} else {
octetString = new OctetString(s);
}
return octetString;
}
private Address createAddress(Configuration properties) {
String host = properties.getSimpleValue("host",null);
String portS = properties.getSimpleValue("port",null);
if (host==null) {
String tmp = systemConfig.getSimpleValue("defaultTargetHost",null);
if ((tmp != null) && (tmp.length() > 0)) {
host=tmp;
}
}
if (portS==null) {
String tmp = systemConfig.getSimpleValue("defaultPort","162");
if ((tmp != null) && (tmp.length() > 0)) {
portS = tmp;
}
}
Integer port = Integer.valueOf(portS);
if (port==0) {
port = 162; // just to make sure
}
String transport = systemConfig.getSimpleValue("transport","UDP");
String address = host + "/" + port;
if (transport.equalsIgnoreCase(UDP_TRANSPORT)) {
return new UdpAddress(address);
} else if (transport.equalsIgnoreCase(TCP_TRANSPORT)) {
return new TcpAddress(address);
}
throw new IllegalArgumentException("Unknown transport: " + transport);
}
protected String getVariableBindings(PDU response) {
StringBuilder strBuf = new StringBuilder();
for (int i = 0; i < response.size(); i++) {
VariableBinding vb = response.get(i);
strBuf.append(vb.toString());
}
return strBuf.toString();
}
protected String getReport(PDU response) {
if (response.size() < 1) {
return "REPORT PDU does not contain a variable binding.";
}
VariableBinding vb = response.get(0);
OID oid = vb.getOid();
log.debug(" Current counter value is " + vb.getVariable() + ".");
if (SnmpConstants.usmStatsUnsupportedSecLevels.equals(oid)) {
return "REPORT: Unsupported Security Level.";
} else if (SnmpConstants.usmStatsNotInTimeWindows.equals(oid)) {
return "REPORT: Message not within time window.";
} else if (SnmpConstants.usmStatsUnknownUserNames.equals(oid)) {
return "REPORT: Unknown user name.";
} else if (SnmpConstants.usmStatsUnknownEngineIDs.equals(oid)) {
return "REPORT: Unknown engine id.";
} else if (SnmpConstants.usmStatsWrongDigests.equals(oid)) {
return "REPORT: Wrong digest.";
} else if (SnmpConstants.usmStatsDecryptionErrors.equals(oid)) {
return "REPORT: Decryption error.";
} else if (SnmpConstants.snmpUnknownSecurityModels.equals(oid)) {
return "REPORT: Unknown security model.";
} else if (SnmpConstants.snmpInvalidMsgs.equals(oid)) {
return "REPORT: Invalid message.";
} else if (SnmpConstants.snmpUnknownPDUHandlers.equals(oid)) {
return "REPORT: Unknown PDU handler.";
} else if (SnmpConstants.snmpUnavailableContexts.equals(oid)) {
return "REPORT: Unavailable context.";
} else if (SnmpConstants.snmpUnknownContexts.equals(oid)) {
return "REPORT: Unknown context.";
} else {
return "REPORT contains unknown OID (" + oid.toString() + ").";
}
}
/**
* processPdu
*
* @param e CommandResponderEvent
*/
public synchronized void processPdu(CommandResponderEvent e) {
PDU command = e.getPDU();
if (command != null) {
if (log.isDebugEnabled())
log.debug(command.toString());
if ((command.getType() != PDU.TRAP) && (command.getType() != PDU.V1TRAP)
&& (command.getType() != PDU.REPORT) && (command.getType() != PDU.RESPONSE)) {
command.setErrorIndex(0);
command.setErrorStatus(0);
command.setType(PDU.RESPONSE);
StatusInformation statusInformation = new StatusInformation();
StateReference ref = e.getStateReference();
try {
e.getMessageDispatcher().returnResponsePdu(e.getMessageProcessingModel(), e.getSecurityModel(),
e.getSecurityName(), e.getSecurityLevel(), command, e.getMaxSizeResponsePDU(), ref,
statusInformation);
} catch (MessageException ex) {
log.error("Error while sending response: " + ex.getMessage());
}
}
}
}
public PDU createPDU(Target target) {
PDU request;
if (target.getVersion() == SnmpConstants.version3) {
request = new ScopedPDU();
ScopedPDU scopedPDU = (ScopedPDU) request;
if (contextEngineID != null) {
scopedPDU.setContextEngineID(contextEngineID);
}
if (contextName != null) {
scopedPDU.setContextName(contextName);
}
} else {
if (pduType == PDU.V1TRAP) {
v1TrapPDU.setTimestamp(sysUpTime.toLong());
request = v1TrapPDU;
} else {
request = new PDU();
}
}
request.setType(pduType);
return request;
}
/**
* This method sends the actual trap
* @param alert The alert to send
* @param alertParameters the notification data (target agent)
* @param platformName the name of the platform the alert is on
* @param conditions a string that shows the alert conditions
* @param bootTime TODO
* @param alertUrl TODO
* @return 'Error code' of the operation
*/
public String sendSnmpTrap(Alert alert, Configuration alertParameters, String platformName, String conditions,
Date bootTime, String alertUrl, String hierarchy) {
if (!this.snmpEnabled) {
return "SNMP is not enabled.";
}
String variableBindingPrefix = alertParameters.getSimpleValue(SnmpInfo.PARAM_VARIABLE_BINDING_PREFIX,
DEFAULT_RHQ_BINDING);
// request id and a timestamp are added below in setSysUpTime..
this.address = createAddress(alertParameters);
// bind the alert definitions name on the oid set in the alert
getVariableBindings(variableBindingPrefix + ".1" + "={s}" + alert.getAlertDefinition().getName());
// the resource the alert was defined on
getVariableBindings(variableBindingPrefix + ".2" + "={s}" + alert.getAlertDefinition().getResource().getName());
// the platform this resource is on
getVariableBindings(variableBindingPrefix + ".3" + "={s}" + platformName);
// the conditions of this alert
getVariableBindings(variableBindingPrefix + ".4" + "={s}" + conditions);
// severity of the alert
getVariableBindings(variableBindingPrefix + ".5" + "={s}" + alert.getAlertDefinition().getPriority().toString().toLowerCase());
// url of the alert detail
getVariableBindings(variableBindingPrefix + ".6" + "={s}" + alertUrl);
// hierarchy of the resource on alert
getVariableBindings(variableBindingPrefix + ".7" + "={s}" + hierarchy);
setSysUpTimeFromBootTime(bootTime); // needs to be called before checkTrapVariables();
setTrapOIDFromAlertParameters(alertParameters); // needs to be called before checkTrapVariables();
checkTrapVariables(this.vbs);
try {
PDU response = send();
if ((getPduType() == PDU.TRAP) || (getPduType() == PDU.V1TRAP)) {
return (PDU.getTypeString(getPduType()) + " sent successfully");
} else if (response == null) {
return ("Request timed out.");
} else if (response.getType() == PDU.REPORT) {
return getReport(response);
} else if (this.operation == DEFAULT) {
return "Response received with requestID=" + response.getRequestID() + ", errorIndex="
+ response.getErrorIndex() + ", " + "errorStatus=" + response.getErrorStatusText() + "("
+ response.getErrorStatus() + ")" + "\n" + getVariableBindings(response);
} else {
return "Received something strange: requestID=" + response.getRequestID() + ", errorIndex="
+ response.getErrorIndex() + ", " + "errorStatus=" + response.getErrorStatusText() + "("
+ response.getErrorStatus() + ")" + "\n" + getVariableBindings(response);
}
} catch (IOException ex) {
log.error(ex.getMessage());
return "Error while trying to send request: " + ex.getMessage();
} catch (IllegalArgumentException ex) {
log.error(ex.getMessage());
return "SNMPAction configured incorrectly: " + ex.getMessage();
}
}
/**
* Calculate the time diff between system boot time and now and
* set the uptime variable from it.
* @param bootTime
*/
private void setSysUpTimeFromBootTime(Date bootTime) {
long now = System.currentTimeMillis();
long delta;
if (bootTime != null) {
delta = now - bootTime.getTime();
} else
delta = 0;
setSysUpTime(new TimeTicks(delta / 100)); // TT is 100th of a second
}
private void setTrapOIDFromAlertParameters(Configuration alertParameters) {
String trapOid = alertParameters.getSimpleValue(SnmpInfo.PARAM_TRAP_OID, null);
if (StringUtil.isNotBlank(trapOid)) {
setTrapOID(new OID(trapOid));
}
}
private boolean init() {
String snmpVersion = systemConfig.getSimpleValue("snmpVersion",null);
if ((snmpVersion != null) && (snmpVersion.length() > 0)) {
if (snmpVersion.equals("1")) {
this.version = SnmpConstants.version1;
this.pduType = PDU.V1TRAP;
} else if (snmpVersion.equals("2c")) {
this.version = SnmpConstants.version2c;
} else if (snmpVersion.equals("3")) {
this.version = SnmpConstants.version3;
} else {
throw new IllegalStateException("SNMP version " + snmpVersion + " is not supported.");
}
} else {
return false;
}
if ((this.pduType == PDU.V1TRAP) && (this.version != SnmpConstants.version1)) {
throw new IllegalStateException("V1TRAP PDU type is only available for SNMP version 1");
}
String tmp = systemConfig.getSimpleValue("authProtocol","MD5");
if ((tmp != null) && (tmp.length() > 0)) {
if (tmp.equals("MD5")) {
this.authProtocol = AuthMD5.ID;
} else if (tmp.equals("SHA")) {
this.authProtocol = AuthSHA.ID;
} else if (tmp.equals("none")) {
this.authProtocol=null;
} else {
throw new IllegalStateException("SNMP authentication protocol unsupported: " + tmp);
}
}
tmp = systemConfig.getSimpleValue("authPassphrase",null);
if ((tmp != null) && (tmp.length() > 0)) {
this.authPassphrase = createOctetString(tmp);
}
tmp = systemConfig.getSimpleValue("privacyPassphrase",null);
if ((tmp != null) && (tmp.length() > 0)) {
this.privPassphrase = createOctetString(tmp);
}
tmp = systemConfig.getSimpleValue("community",null);
if ((tmp != null) && (tmp.length() > 0)) {
this.community = createOctetString(tmp);
}
tmp = systemConfig.getSimpleValue("engineId",null);
if ((tmp != null) && (tmp.length() > 0)) {
this.contextEngineID = createOctetString(tmp);
}
tmp = systemConfig.getSimpleValue("targetContext",null);
if ((tmp != null) && (tmp.length() > 0)) {
this.contextName = createOctetString(tmp);
}
tmp = systemConfig.getSimpleValue("securityName",null);
if ((tmp != null) && (tmp.length() > 0)) {
this.securityName = createOctetString(tmp);
}
tmp = systemConfig.getSimpleValue("trapOid",null);
if ((tmp != null) && (tmp.length() > 0)) {
this.trapOID = new OID(tmp);
}
tmp = systemConfig.getSimpleValue("enterpriseOid",null);
if ((tmp != null) && (tmp.length() > 0)) {
this.v1TrapPDU.setEnterprise(new OID(tmp));
}
tmp = systemConfig.getSimpleValue("genericId",null);
if ((tmp != null) && (tmp.length() > 0)) {
this.v1TrapPDU.setGenericTrap(Integer.parseInt(tmp));
}
tmp = systemConfig.getSimpleValue("specificId",null);
if ((tmp != null) && (tmp.length() > 0)) {
this.v1TrapPDU.setSpecificTrap(Integer.parseInt(tmp));
}
tmp = systemConfig.getSimpleValue("agentAddress",null);
if ((tmp != null) && (tmp.length() > 0)) {
this.v1TrapPDU.setAgentAddress(new IpAddress(tmp));
}
tmp = systemConfig.getSimpleValue("privacyProtocol","AES");
if ((tmp != null) && (tmp.length() > 0)) {
if (tmp.equals("DES")) {
this.privProtocol = PrivDES.ID;
} else if ((tmp.equals("AES128")) || (tmp.equals("AES"))) {
this.privProtocol = PrivAES128.ID;
} else if (tmp.equals("AES192")) {
this.privProtocol = PrivAES192.ID;
} else if (tmp.equals("AES256")) {
this.privProtocol = PrivAES256.ID;
} else {
throw new IllegalArgumentException("Privacy protocol " + tmp + " not supported");
}
}
// Set the default counter listener to return proper USM and MP error counters.
CounterSupport.getInstance().addCounterListener(new DefaultCounterListener());
return true;
}
/*=== Getters and Setters ==============================================*/
public OctetString getAuthPassphrase() {
return authPassphrase;
}
public void setAuthPassphrase(OctetString authPassphrase) {
this.authPassphrase = authPassphrase;
}
public OID getAuthProtocol() {
return authProtocol;
}
public void setAuthProtocol(OID authProtocol) {
this.authProtocol = authProtocol;
}
public OctetString getCommunity() {
return community;
}
public void setCommunity(OctetString community) {
this.community = community;
}
public OctetString getContextEngineID() {
return contextEngineID;
}
public void setContextEngineID(OctetString contextEngineID) {
this.contextEngineID = contextEngineID;
}
public void setContextName(OctetString contextName) {
this.contextName = contextName;
}
public OctetString getContextName() {
return contextName;
}
public int getMaxRepetitions() {
return maxRepetitions;
}
public void setMaxRepetitions(int maxRepetitions) {
this.maxRepetitions = maxRepetitions;
}
public int getNonRepeaters() {
return nonRepeaters;
}
public void setNonRepeaters(int nonRepeaters) {
this.nonRepeaters = nonRepeaters;
}
public int getOperation() {
return operation;
}
public void setOperation(int operation) {
this.operation = operation;
}
public OctetString getPrivPassphrase() {
return privPassphrase;
}
public void setPrivPassphrase(OctetString privPassphrase) {
this.privPassphrase = privPassphrase;
}
public OID getPrivProtocol() {
return privProtocol;
}
public void setPrivProtocol(OID privProtocol) {
this.privProtocol = privProtocol;
}
public int getRetries() {
return retries;
}
public void setRetries(int retries) {
this.retries = retries;
}
public OctetString getSecurityName() {
return securityName;
}
public void setSecurityName(OctetString securityName) {
this.securityName = securityName;
}
public TimeTicks getSysUpTime() {
return sysUpTime;
}
public void setSysUpTime(TimeTicks sysUpTime) {
this.sysUpTime = sysUpTime;
}
public Target getTarget() {
return target;
}
public void setTarget(Target target) {
this.target = target;
}
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public OID getTrapOID() {
return trapOID;
}
public void setTrapOID(OID trapOID) {
this.trapOID = trapOID;
}
public List getVbs() {
return vbs;
}
public void setVbs(ArrayList vbs) {
this.vbs = vbs;
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
public int getPduType() {
return pduType;
}
public void setPduType(int pduType) {
this.pduType = pduType;
}
}