import os,sys,logging,hashlib,time,errno,subprocess import xml.dom.minidom, xml.sax.saxutils import time import threading import socket import signal from subprocess import Popen,PIPE from logging.handlers import TimedRotatingFileHandler from splunklib.client import Service SPLUNK_HOME = os.environ.get("SPLUNK_HOME") MYAPP = 'snmp_ta' # Set up a specific logger logger = logging.getLogger('snmpmodinput') logger.propagate = False #default logging level , can be overidden in stanza config logger.setLevel(logging.INFO) RESPONSE_HANDLER_INSTANCE = None base_app_dir = os.path.join(SPLUNK_HOME,"etc","apps",MYAPP) base_mibs_dir = os.path.join(base_app_dir,"bin","mibs") #Retained for legacy support #dynamically load in any eggs egg_dir = os.path.join(base_app_dir,"bin") for filename in os.listdir(egg_dir): if filename.endswith(".egg"): sys.path.append(os.path.join(egg_dir ,filename)) #Retained for legacy support #directory of user MIB python modules mib_egg_dir = base_mibs_dir sys.path.append(mib_egg_dir) for filename in os.listdir(mib_egg_dir): if filename.endswith(".egg"): sys.path.append(os.path.join(mib_egg_dir ,filename)) #new locations of user MIBs since v1.7 #plaintext mibs mib_user_plaintext_dir = os.path.join(base_mibs_dir,"user_plaintext_mibs") mib_common_plaintext_dir = os.path.join(base_mibs_dir,"common_plaintext_mibs") #precompiled/compiled MIBs mib_python_dir = os.path.join(base_mibs_dir,"user_python_mibs") sys.path.append(mib_python_dir) from pysnmp.entity.rfc3413.oneliner import cmdgen from pysnmp.carrier.asynsock.dispatch import AsynsockDispatcher from pysnmp.carrier.asynsock.dgram import udp, udp6 from pysnmp.entity import engine from pysnmp.entity import config as pysnmp_config from pyasn1.codec.ber import decoder from pysnmp.proto import api,rfc1155,rfc1902 from pysnmp.smi import builder from pysnmp.entity.rfc3413 import mibvar,ntfrcv from pysnmp.smi import view from pysnmp.smi import compiler from pysnmp.proto.api import v2c from pysnmp.debug import setLogger, Debug, Printer # Patch pysnmp for COUNTER/TIMETICKS encodings that might decode into a negative value def counterCloneHack(self, *args): if args and args[0] < 0: args = (0xffffffff+args[0]-1,) + args[1:] return self.__class__(*args) rfc1155.Counter.clone = counterCloneHack rfc1155.TimeTicks.clone = counterCloneHack rfc1902.Counter32.clone = counterCloneHack SCHEME = """ SNMP SNMP input to poll attribute values and catch traps true xml false SNMP Input Name Name of this SNMP input Activation Key Visit http://www.baboonbones.com/#activation to obtain a non-expiring key true true SNMP Mode Whether or not this stanza is for polling attributes or listening for traps false false Destination IP or hostname of the device you would like to query,or a comma delimited list false false IP Version 6 Whether or not this is an IP version 6 address. Defaults to false false false Port The SNMP port. Defaults to 161 false false SNMP Version The SNMP Version , 1 or 2C, version 3 not currently supported. Defaults to 2C false false Object Names 1 or more Objects Names , comma delimited , in either textual(iso.org.dod.internet.mgmt.mib-2.system.sysDescr.0) or numerical(1.3.6.1.2.1.1.3.0) format. By default a GET operation will be executed. If you require bulk operations , then select a SNMP Walking option false false Community String Community String used for authentication.Defaults to "public" false false SNMPv3 USM Username SNMPv3 USM Username false false SNMPv3 Engine ID SNMPv3 Engine ID false false SNMPv3 Authorization Key SNMPv3 secret authorization key used within USM for SNMP PDU authorization. Setting it to a non-empty value implies MD5-based PDU authentication (defaults to usmHMACMD5AuthProtocol) to take effect. Default hashing method may be changed by means of further authProtocol parameter false false SNMPv3 Encryption Key SNMPv3 secret encryption key used within USM for SNMP PDU encryption. Setting it to a non-empty value implies MD5-based PDU authentication (defaults to usmHMACMD5AuthProtocol) and DES-based encryption (defaults to usmDESPrivProtocol) to take effect. Default hashing and/or encryption methods may be changed by means of further authProtocol and/or privProtocol parameters. false false SNMPv3 Authorization Protocol may be used to specify non-default hash function algorithm. Possible values include usmHMACMD5AuthProtocol (default) / usmHMACSHAAuthProtocol / usmNoAuthProtocol false false SNMPv3 Encryption Key Protocol may be used to specify non-default ciphering algorithm. Possible values include usmDESPrivProtocol (default) / usmAesCfb128Protocol / usm3DESEDEPrivProtocol / usmAesCfb192Protocol / usmAesCfb256Protocol / usmNoPrivProtocol false false Interval How often to run the SNMP query (in seconds). Defaults to 60 seconds false false Timeout SNMP attribute polling timeout (in seconds). Defaults to 1 second. NOTE: timer resolution is about 0.5 seconds false false Automatic Retries Number of times to automatically retry polling before giving up. Defaults to 5 false false Perform a SNMPWALK using GETBULK Defaults to false false false Perform a SNMPWALK using GETNEXT Defaults to false false false Split Bulk Results Whether or not to split up bulk output into individual events. Defaults to false. false false Lexicographic Mode (for all SNMP Walking operations) Walk SNMP agent's MIB till the end (if true), otherwise (if false) stop iteration when all response MIB variables leave the scope of initial MIB variables in the OID list. Default is false. false false Non Repeaters (for GETBULK operations) The number of objects that are only expected to return a single GETNEXT instance, not multiple instances. Defaults to 0. false false Max Repetitions (for GETBULK operations) The number of objects that should be returned for all the repeating OIDs.Defaults to 25. false false Listen for TRAP messages Whether or not to listen for TRAP messages false false TRAP listener port TRAP listener port. Defaults to 162.Ensure that you have the necessary OS user permissions for port values 0-1024 false false TRAP listener host TRAP listener host. Defaults to localhost.Ensure that you set this to the Hostname or IP that the trap client is sending to. false false TRAP Origin Reverse DNS Lookup TRAP Generating Host reverse DNS lookup. Forces host field to the DNS lookup if available insted of IP address. Defaults to false. Be aware may cause large DNS lookup volume false false MIB Names Comma delimited list of MIB names to be applied ie: IF-MIB,DNS-SERVER-MIB,BRIDGE-MIB false false Response Handler Python classname of custom response handler false false Response Handler Arguments Response Handler arguments string , key=value,key2=value2 false false Log Level Logging level (info, error, debug etc..) false false Use System Python Whether or not to use a System python runtime vs Splunk's built in python runtime. Defaults to false. false false System Python Path Defaults to /usr/bin/python false false Run Process Checker Whether or not to autonomously manage the script's running state.Defaults to true. false false """ def get_credentials(): result = [] try: for sp in SDK_SERVICE.storage_passwords: values = {} values['username'] = sp.username or "none" values['clear_password'] = sp.clear_password or "none" result.append(values) except Exception as e: logger.error("Could not get credentials from splunk. Error: %s" % str(e)) return result return result def do_validate(): try: config = get_validation_config() port=config.get("port") trap_port=config.get("trap_port") snmpinterval=config.get("snmpinterval") timeout=config.get("timeout") retries=config.get("retries") max_repetitions=config.get("max_repetitions") non_repeaters=config.get("non_repeaters") validationFailed = False if not port is None and int(port) < 1: print_validation_error("Port value must be a positive integer") validationFailed = True if not trap_port is None and int(trap_port) < 1: print_validation_error("Trap port value must be a positive integer") validationFailed = True if not non_repeaters is None and int(non_repeaters) < 0: print_validation_error("Non Repeaters value must be zero or a positive integer") validationFailed = True if not max_repetitions is None and int(max_repetitions) < 0: print_validation_error("Max Repetitions value must be zero or a positive integer") validationFailed = True if not snmpinterval is None and int(snmpinterval) < 1: print_validation_error("SNMP Polling interval must be a positive integer") validationFailed = True if not timeout is None and float(timeout) < 0: print_validation_error("SNMP Polling timeout must not be a negative number") validationFailed = True if validationFailed: sys.exit(2) except: # catch *all* exceptions e = sys.exc_info()[1] sys.exit(1) raise # Given an ip, return the host. Return the ip if no lookup found or if lookup disabled by configuration def rlookup(ip): if trap_rdns: try: hostname, aliaslist, ipaddrlist = socket.gethostbyaddr(ip) return hostname except: return ip else: return ip def v3trapCallback(snmpEngine,stateReference,contextEngineId, contextName,varBinds,cbCtx): try: logger.info("V3 Trap Received") trap_metadata = "" server = "" ( transportDomain,transportAddress ) = snmpEngine.msgAndPduDsp.getTransportInfo(stateReference) try: server = "%s" % rlookup(transportAddress[0]) trap_metadata += 'notification_from_address = "%s" ' % (transportAddress[0]) trap_metadata += 'notification_from_domain = "%s" ' % (transportDomain[0]) except: # catch *all* exceptions e = sys.exc_info()[1] logger.error("Exception resolving source address/domain of the trap: %s" % str(e)) try: trap_metadata += 'context_engine_id = "%s" ' % (contextEngineId.prettyPrint()) trap_metadata += 'context_name = "%s" ' % (contextName.prettyPrint()) except: # catch *all* exceptions e = sys.exc_info()[1] logger.error("Exception resolving context of the trap: %s" % str(e)) handle_output(mibview_controller,varBinds,server,from_trap=True,trap_metadata=trap_metadata) except: # catch *all* exceptions e = sys.exc_info()[1] logger.error("Exception receiving trap %s" % str(e)) def trapCallback(transportDispatcher, transportDomain, transportAddress, wholeMsg): try: if not wholeMsg: logger.error('Receiving trap , error processing the inital message in the trapCallback handler') while wholeMsg: msgVer = int(api.decodeMessageVersion(wholeMsg)) logger.info("Trap Received") if msgVer in api.protoModules: pMod = api.protoModules[msgVer] else: logger.error('Receiving trap , unsupported SNMP version %s' % msgVer) return reqMsg, wholeMsg = decoder.decode(wholeMsg, asn1Spec=pMod.Message(),) reqPDU = pMod.apiMessage.getPDU(reqMsg) trap_metadata ="" server = "" try: server = "%s" % rlookup((transportAddress[0])) trap_metadata += 'notification_from_address = "%s" ' % (transportAddress[0]) trap_metadata += 'notification_from_port = "%s" ' % (transportAddress[1]) except: # catch *all* exceptions e = sys.exc_info()[1] logger.error("Exception resolving source address/domain of the trap: %s" % str(e)) if reqPDU.isSameTypeWith(pMod.TrapPDU()): if msgVer == api.protoVersion1: if server == "": server = pMod.apiTrapPDU.getAgentAddr(reqPDU).prettyPrint() trap_metadata += 'notification_enterprise = "%s" ' % (pMod.apiTrapPDU.getEnterprise(reqPDU).prettyPrint()) trap_metadata += 'notification_agent_address = "%s" ' % (pMod.apiTrapPDU.getAgentAddr(reqPDU).prettyPrint()) trap_metadata += 'notification_generic_trap = "%s" ' % (pMod.apiTrapPDU.getGenericTrap(reqPDU).prettyPrint()) trap_metadata += 'notification_specific_trap = "%s" ' % (pMod.apiTrapPDU.getSpecificTrap(reqPDU).prettyPrint()) trap_metadata += 'notification_uptime = "%s" ' % (pMod.apiTrapPDU.getTimeStamp(reqPDU).prettyPrint()) varBinds = pMod.apiTrapPDU.getVarBinds(reqPDU) else: varBinds = pMod.apiPDU.getVarBinds(reqPDU) handle_output(mibview_controller,varBinds,server,from_trap=True,trap_metadata=trap_metadata) except: # catch *all* exceptions e = sys.exc_info()[1] logger.error("Exception receiving trap %s" % str(e)) return wholeMsg def do_run(config): logger.info("SNMP Modular Input executing") global SESSION_TOKEN global SPLUNK_PORT global SDK_SERVICE SESSION_TOKEN = config.get("session_key") server_uri = config.get("server_uri") SPLUNK_PORT = server_uri[18:] #get SDK Service handle args = {'host':'localhost','port':SPLUNK_PORT,'token':SESSION_TOKEN,'owner':'nobody','app':MYAPP} SDK_SERVICE = Service(**args) activation_key = config.get("activation_key").strip() app_name = "SNMP Modular Input" if len(activation_key) > 32: activation_hash = activation_key[:32] activation_ts = activation_key[32:][::-1] current_ts = time.time() m = hashlib.md5() m.update((app_name + activation_ts).encode('utf-8')) if not m.hexdigest().upper() == activation_hash.upper(): logger.error("Trial Activation key for App '%s' failed. Please ensure that you copy/pasted the key correctly." % app_name) sys.exit(2) if ((current_ts - int(activation_ts)) > 604800): logger.error("Trial Activation key for App '%s' has now expired. Please visit http://www.baboonbones.com/#activation to purchase a non expiring key." % app_name) sys.exit(2) else: m = hashlib.md5() m.update((app_name).encode('utf-8')) if not m.hexdigest().upper() == activation_key.upper(): logger.error("Activation key for App '%s' failed. Please ensure that you copy/pasted the key correctly." % app_name) sys.exit(2) credentials_list = get_credentials() for c in credentials_list: replace_key='{encrypted:%s}' % c['username'] for k, v in config.items(): config[k] = v.replace(replace_key,c['clear_password']) #params snmp_mode=config.get("snmp_mode","") destination_list=config.get("destination") if not destination_list is None: destinations = list(map(str,destination_list.split(","))) #trim any whitespace using a list comprehension destinations = [x.strip(' ') for x in destinations] port=int(config.get("port",161)) snmpinterval=int(config.get("snmpinterval",60)) timeout_val=float(config.get("timeout",1.0)) num_retries=int(config.get("retries",5)) ipv6=int(config.get("ipv6",0)) response_handler_args={} response_handler_args_str=config.get("response_handler_args") if not response_handler_args_str is None: response_handler_args = dict((k.strip(), v.strip()) for k,v in (item.split('=') for item in response_handler_args_str.split(','))) response_handler=config.get("response_handler","DefaultResponseHandler") module = __import__("responsehandlers") class_ = getattr(module,response_handler) global RESPONSE_HANDLER_INSTANCE RESPONSE_HANDLER_INSTANCE = class_(**response_handler_args) #snmp 1 and 2C params snmp_version=config.get("snmp_version","2C") communitystring=config.get("communitystring","public") v3_securityName=config.get("v3_securityName","") v3_securityEngineId=config.get("v3_securityEngineId",None) v3_authKey=config.get("v3_authKey",None) v3_privKey=config.get("v3_privKey",None) v3_authProtocol_str=config.get("v3_authProtocol","usmHMACMD5AuthProtocol") v3_privProtocol_str=config.get("v3_privProtocol","usmDESPrivProtocol") v3_authProtocol = get_v3_authProtocol_obj(v3_authProtocol_str) v3_privProtocol = get_v3_privProtocol_obj(v3_privProtocol_str) #object names to poll object_names=config.get("object_names") if not object_names is None: oid_args = list(map(str,object_names.split(","))) #trim any whitespace using a list comprehension oid_args = [x.strip(' ') for x in oid_args] #GET BULK params do_subtree=int(config.get("do_get_subtree",0)) do_bulk=int(config.get("do_bulk_get",0)) split_bulk_output=int(config.get("split_bulk_output",0)) lexicographic_mode=int(config.get("lexicographic_mode",0)) non_repeaters=int(config.get("non_repeaters",0)) max_repetitions=int(config.get("max_repetitions",25)) #TRAP listener params listen_traps=int(config.get("listen_traps",0)) #some backwards compatibility gymnastics if snmp_mode == 'traps': listen_traps = 1 trap_port=int(config.get("trap_port",162)) trap_host=config.get("trap_host","localhost") global trap_rdns trap_rdns=int(config.get("trap_rdns",0)) #MIBs to load mib_names=config.get("mib_names") mib_names_args=None if not mib_names is None: mib_names_args = list(map(str,mib_names.split(","))) #trim any whitespace using a list comprehension mib_names_args = [x.strip(' ') for x in mib_names_args] logger.info("Building MIBs") try: mibBuilder = builder.MibBuilder() global mibview_controller mibview_controller = view.MibViewController(mibBuilder) mibBuilder.addMibSources(builder.DirMibSource(mib_egg_dir)) mibBuilder.addMibSources(builder.DirMibSource(mib_python_dir)) for filename in os.listdir(mib_egg_dir): if filename.endswith(".egg"): mibBuilder.addMibSources(builder.ZipMibSource(filename)) compiler.addMibCompiler(mibBuilder, sources=['file://'+mib_user_plaintext_dir,'file://'+mib_common_plaintext_dir], destination=mib_python_dir) if mib_names_args: mibBuilder.loadModules(*mib_names_args) except: # catch *all* exceptions e = sys.exc_info()[1] logger.error("Looks like an error building/loading your MIBs, ensure that you have all the required MIBs as well any MIB dependencies satisfied from the MIB file IMPORT section : %s" % str(e)) if listen_traps: logger.info("Running in Trap Listener mode") if snmp_version == "1" or snmp_version == "2C": trapThread = TrapThread(trap_port,trap_host,ipv6) trapThread.start() if snmp_version == "3": trapThread = V3TrapThread(trap_port,trap_host,ipv6,v3_securityName,v3_securityEngineId,v3_authKey,v3_authProtocol,v3_privKey,v3_privProtocol) trapThread.start() if not (object_names is None) and not(destination_list is None) and not listen_traps: logger.info("Running in Attribute Polling mode") mp_model_val=1 if snmp_version == "1": mp_model_val=0 for destination in destinations: apt = AttributePollerThread(destination,port,ipv6,snmp_version,do_bulk,do_subtree,lexicographic_mode,snmpinterval,non_repeaters,max_repetitions,oid_args,split_bulk_output,mp_model_val,timeout_val,num_retries,communitystring,v3_securityName,v3_authKey,v3_privKey,v3_authProtocol,v3_privProtocol) apt.start() class AttributePollerThread(threading.Thread): def __init__(self,destination,port,ipv6,snmp_version,do_bulk,do_subtree,lexicographic_mode,snmpinterval,non_repeaters,max_repetitions,oid_args,split_bulk_output,mp_model_val,timeout_val,num_retries,communitystring,v3_securityName,v3_authKey,v3_privKey,v3_authProtocol,v3_privProtocol): threading.Thread.__init__(self) self.ipv6=ipv6 self.mp_model_val=mp_model_val self.timeout_val=timeout_val self.num_retries=num_retries self.communitystring=communitystring self.v3_securityName=v3_securityName self.v3_authKey=v3_authKey self.v3_privKey=v3_privKey self.v3_authProtocol=v3_authProtocol self.v3_privProtocol=v3_privProtocol self.destination=destination self.port=port self.snmp_version=snmp_version self.do_bulk=do_bulk self.do_subtree=do_subtree self.lexicographic_mode=bool(lexicographic_mode) self.snmpinterval=snmpinterval self.non_repeaters=non_repeaters self.max_repetitions=max_repetitions self.oid_args=oid_args self.split_bulk_output=split_bulk_output self.initialize() def initialize(self): self.cmdGen = cmdgen.CommandGenerator() if self.snmp_version == "3": self.security_object = cmdgen.UsmUserData( self.v3_securityName, authKey=self.v3_authKey, privKey=self.v3_privKey, authProtocol=self.v3_authProtocol, privProtocol=self.v3_privProtocol ) else: self.security_object = cmdgen.CommunityData(self.communitystring,mpModel=self.mp_model_val) if self.ipv6: self.transport = cmdgen.Udp6TransportTarget((self.destination, self.port), timeout=self.timeout_val, retries=self.num_retries) else: self.transport = cmdgen.UdpTransportTarget((self.destination, self.port), timeout=self.timeout_val, retries=self.num_retries) logger.info("Attribute Poller initialized") def reinitialize(self): self.initialize() def run(self): try: while True: if self.do_bulk and not self.snmp_version == "1": try: logger.info("Performing SNMP Walk using GETBULK Command") errorIndication, errorStatus, errorIndex, varBindTable = self.cmdGen.bulkCmd( self.security_object, self.transport, self.non_repeaters, self.max_repetitions, *self.oid_args, lookupNames=True, lookupValues=True, lexicographicMode=self.lexicographic_mode) except: # catch *all* exceptions e = sys.exc_info()[1] logger.error("Exception with bulkCmd to %s:%s: %s" % (self.destination, self.port, str(e))) time.sleep(float(self.snmpinterval)) self.reinitialize() continue elif self.do_subtree and not self.snmp_version == "1": try: logger.info("Performing SNMP Walk using GETNEXT Command") errorIndication, errorStatus, errorIndex, varBindTable = self.cmdGen.nextCmd( self.security_object, self.transport, *self.oid_args, lookupNames=True, lookupValues=True, lexicographicMode=self.lexicographic_mode) except: # catch *all* exceptions e = sys.exc_info()[1] logger.error("Exception with nextCmd to %s:%s: %s" % (self.destination, self.port, str(e))) time.sleep(float(self.snmpinterval)) self.reinitialize() continue else: try: logger.info("Sending GET Command") errorIndication, errorStatus, errorIndex, varBinds = self.cmdGen.getCmd( self.security_object, self.transport, *self.oid_args, lookupNames=True, lookupValues=True) except: # catch *all* exceptions e = sys.exc_info()[1] logger.error("Exception with getCmd to %s:%s: %s" % (self.destination, self.port, str(e))) time.sleep(float(self.snmpinterval)) self.reinitialize() continue if errorIndication: logger.error(errorIndication) self.reinitialize() elif errorStatus: logger.error(errorStatus) self.reinitialize() else: if self.do_bulk: handle_output(mibview_controller,varBindTable,self.destination,table=True,split_bulk_output=self.split_bulk_output) elif self.do_subtree: handle_output(mibview_controller,varBindTable,self.destination,table=True,split_bulk_output=self.split_bulk_output) else: handle_output(mibview_controller,varBinds,self.destination,table=False,split_bulk_output=self.split_bulk_output) time.sleep(float(self.snmpinterval)) except: # catch *all* exceptions e = sys.exc_info()[1] logger.error("Looks like an error: %s" % str(e)) logger.error('Error on line {}'.format(sys.exc_info()[-1].tb_lineno)) sys.exit(1) class TrapThread(threading.Thread): def __init__(self,port,host,ipv6): threading.Thread.__init__(self) self.port=port self.host=host self.ipv6=ipv6 def run(self): transportDispatcher = AsynsockDispatcher() transportDispatcher.registerRecvCbFun(trapCallback) if self.ipv6: transport = udp6.Udp6SocketTransport() domainName = udp6.domainName else: transport = udp.UdpSocketTransport() domainName = udp.domainName try: transportDispatcher.registerTransport(domainName, transport.openServerMode((self.host, self.port))) transportDispatcher.jobStarted(1) logger.info("Trap Listener initialized , listening on host %s port %s" % (self.host, self.port)) # Dispatcher will never finish as job#1 never reaches zero transportDispatcher.runDispatcher() except: # catch *all* exceptions e = sys.exc_info()[1] transportDispatcher.closeDispatcher() logger.error("Failed to register transport and run dispatcher: %s" % str(e)) sys.exit(1) class V3TrapThread(threading.Thread): def __init__(self,port,host,ipv6,user,engine_id,auth_key,auth_proto,priv_key,priv_proto): threading.Thread.__init__(self) self.port=port self.host=host self.ipv6=ipv6 self.user=user self.engine_id=engine_id self.auth_key=auth_key self.auth_proto=auth_proto self.priv_key=priv_key self.priv_proto=priv_proto def run(self): try: snmpEngine = engine.SnmpEngine() logger.info("Adding a UDP Transport for SNMPv3 Trap Listening, host %s , port %s" % (self.host,self.port)) if self.ipv6: domainName = udp6.domainName pysnmp_config.addTransport(snmpEngine,domainName,udp6.Udp6Transport().openServerMode((self.host, self.port))) else: domainName = udp.domainName pysnmp_config.addTransport(snmpEngine,domainName,udp.UdpTransport().openServerMode((self.host, self.port))) logger.info("Added UDP Transport for SNMPv3 Trap Listening") if self.user and self.auth_proto and self.auth_key and self.priv_proto and self.priv_key: logger.info("Adding a SNMPv3 USM User entry for %s from inputs.conf definition" % self.user) try: #this is the legacy functionality , left in for backwards compatibility , that will only add a single SNMPv3 USM User if self.engine_id is None: pysnmp_config.addV3User(snmpEngine, self.user,self.auth_proto, self.auth_key,self.priv_proto,self.priv_key) else: pysnmp_config.addV3User(snmpEngine, self.user,self.auth_proto, self.auth_key,self.priv_proto,self.priv_key,securityEngineId=v2c.OctetString(hexValue=self.engine_id)) except: # catch *all* exceptions e = sys.exc_info()[1] logger.error("Error adding SNNPv3 USM User: %s" % str(e)) #this is the newer functionality that will build a table of multiple SNMPv3 USM Users from the snmpv3_usm_users.conf file build_snmpv3_usm_users_table(snmpEngine) # Register SNMP Application at the SNMP engine ntfrcv.NotificationReceiver(snmpEngine, v3trapCallback) snmpEngine.transportDispatcher.jobStarted(1) # this job would never finish logger.info("V3 Trap Listener initialized , listening on host %s port %s" % (self.host, self.port)) # Run I/O dispatcher which would receive queries and send confirmations snmpEngine.transportDispatcher.runDispatcher() except: # catch *all* exceptions e = sys.exc_info()[1] snmpEngine.transportDispatcher.closeDispatcher() logger.error("Looks like an error starting the V3 Trap listener: %s" % str(e)) sys.exit(1) # prints validation error data to be consumed by Splunk def print_validation_error(s): print("%s" % xml.sax.saxutils.escape(s)) def handle_output(mibView,response_object,destination,table=False,from_trap=False,trap_metadata=None,split_bulk_output=False): try: if destination == "": destination = os.environ.get("SPLUNK_SERVER") RESPONSE_HANDLER_INSTANCE(response_object,destination,logger,table=table,from_trap=from_trap,trap_metadata=trap_metadata,split_bulk_output=split_bulk_output,mibView=mibView) sys.stdout.flush() except: e = sys.exc_info()[1] logger.error("Looks like an error handling the response output: %s" % str(e)) def usage(): print("usage: %s [--scheme|--validate-arguments]") sys.exit(2) def do_scheme(): print(SCHEME) def get_input_config(config_str): config = {} try: # read everything from stdin # config_str = sys.stdin.read() # parse the config XML doc = xml.dom.minidom.parseString(config_str) root = doc.documentElement session_key_node = root.getElementsByTagName("session_key")[0] if session_key_node and session_key_node.firstChild and session_key_node.firstChild.nodeType == session_key_node.firstChild.TEXT_NODE: data = session_key_node.firstChild.data config["session_key"] = data server_uri_node = root.getElementsByTagName("server_uri")[0] if server_uri_node and server_uri_node.firstChild and server_uri_node.firstChild.nodeType == server_uri_node.firstChild.TEXT_NODE: data = server_uri_node.firstChild.data config["server_uri"] = data conf_node = root.getElementsByTagName("configuration")[0] if conf_node: stanza = conf_node.getElementsByTagName("stanza")[0] if stanza: stanza_name = stanza.getAttribute("name") if stanza_name: config["name"] = stanza_name params = stanza.getElementsByTagName("param") for param in params: param_name = param.getAttribute("name") if param_name and param.firstChild and \ param.firstChild.nodeType == param.firstChild.TEXT_NODE: data = param.firstChild.data config[param_name] = data checkpnt_node = root.getElementsByTagName("checkpoint_dir")[0] if checkpnt_node and checkpnt_node.firstChild and \ checkpnt_node.firstChild.nodeType == checkpnt_node.firstChild.TEXT_NODE: config["checkpoint_dir"] = checkpnt_node.firstChild.data if not config: raise Exception("Invalid configuration received from Splunk.") except Exception as e: raise Exception("Error getting Splunk configuration via STDIN: %s" % str(e)) return config #build a table of SNMPv3 USM Users from the snmpv3_usm_users.conf file def build_snmpv3_usm_users_table(snmpEngine): try: logger.info("Building SNMPv3 USM Users table from snmpv3_usm_users.conf") #get the usm users conf file USM_CONF_FILE = "snmpv3_usm_users" try: USM_CONF = SDK_SERVICE.confs[USM_CONF_FILE] except: # catch *all* exceptions e = sys.exc_info()[1] logger.warning("No SNMPv3 USM users conf file found : %s" % str(e)) return #set global defaults common_settings = {} common_settings["v3_securityName"] = "" common_settings["v3_authProtocol"] = "usmHMACMD5AuthProtocol" common_settings["v3_privProtocol"] = "usmDESPrivProtocol" common_settings["v3_securityEngineId"] = None common_settings["v3_authKey"] = None common_settings["v3_privKey"] = None COMMON_SETTINGS_STANZA_NAME = "common_settings" #update common settings with anything set in the conf file if USM_CONF.__contains__(COMMON_SETTINGS_STANZA_NAME): COMMON_SETTINGS_STANZA_OBJECT = USM_CONF[COMMON_SETTINGS_STANZA_NAME] #prune out None values so our defaults kick in COMMON_SETTINGS_STANZA = {k: v for k, v in COMMON_SETTINGS_STANZA_OBJECT.content().items() if v is not None} common_settings.update(COMMON_SETTINGS_STANZA) #iterate over each "usm_" stanza and add to usm users table for USM_STANZA_OBJECT in USM_CONF: stanza_name = USM_STANZA_OBJECT.name #don't process common_settings again if stanza_name.startswith("usm_"): #prune out None values so our defaults kick in usm_stanza = {k: v for k, v in USM_STANZA_OBJECT.content().items() if v is not None} v3_securityName = usm_stanza.get("v3_securityName",common_settings["v3_securityName"]) v3_securityEngineId = usm_stanza.get("v3_securityEngineId",common_settings["v3_securityEngineId"]) v3_authKey = usm_stanza.get("v3_authKey",common_settings["v3_authKey"]) v3_privKey = usm_stanza.get("v3_privKey",common_settings["v3_privKey"]) v3_authProtocol_str = usm_stanza.get("v3_authProtocol",common_settings["v3_authProtocol"]) v3_privProtocol_str = usm_stanza.get("v3_privProtocol",common_settings["v3_privProtocol"]) v3_authProtocol = get_v3_authProtocol_obj(v3_authProtocol_str) v3_privProtocol = get_v3_privProtocol_obj(v3_privProtocol_str) if v3_securityName and v3_authProtocol and v3_authKey and v3_privProtocol and v3_privKey: logger.info("Adding SNMPv3 USM User entry from %s stanza" % stanza_name) try: if v3_securityEngineId is None: pysnmp_config.addV3User(snmpEngine, v3_securityName,v3_authProtocol, v3_authKey,v3_privProtocol,v3_privKey) else: pysnmp_config.addV3User(snmpEngine, v3_securityName,v3_authProtocol, v3_authKey,v3_privProtocol,v3_privKey,securityEngineId=v2c.OctetString(hexValue=v3_securityEngineId)) except: # catch *all* exceptions e = sys.exc_info()[1] logger.error("Error adding SNNPv3 USM User: %s" % str(e)) logger.info("Completed Building SNMPv3 USM Users table") except: # catch *all* exceptions e = sys.exc_info()[1] logger.error("Error building the SNMPv3 USM Users table : %s" % str(e)) def get_v3_authProtocol_obj(v3_authProtocol_str): if v3_authProtocol_str == "usmHMACMD5AuthProtocol": v3_authProtocol = cmdgen.usmHMACMD5AuthProtocol elif v3_authProtocol_str == "usmHMACSHAAuthProtocol": v3_authProtocol = cmdgen.usmHMACSHAAuthProtocol elif v3_authProtocol_str == "usmHMAC128SHA224AuthProtocol": v3_authProtocol = cmdgen.usmHMAC128SHA224AuthProtocol elif v3_authProtocol_str == "usmHMAC192SHA256AuthProtocol": v3_authProtocol = cmdgen.usmHMAC192SHA256AuthProtocol elif v3_authProtocol_str == "usmHMAC256SHA384AuthProtocol": v3_authProtocol = cmdgen.usmHMAC256SHA384AuthProtocol elif v3_authProtocol_str == "usmHMAC384SHA512AuthProtocol": v3_authProtocol = cmdgen.usmHMAC384SHA512AuthProtocol elif v3_authProtocol_str == "usmNoAuthProtocol": v3_authProtocol = cmdgen.usmNoAuthProtocol else: v3_authProtocol = cmdgen.usmNoAuthProtocol return v3_authProtocol def get_v3_privProtocol_obj(v3_privProtocol_str): if v3_privProtocol_str == "usmDESPrivProtocol": v3_privProtocol = cmdgen.usmDESPrivProtocol elif v3_privProtocol_str == "usm3DESEDEPrivProtocol": v3_privProtocol = cmdgen.usm3DESEDEPrivProtocol elif v3_privProtocol_str == "usmAesCfb128Protocol": v3_privProtocol = cmdgen.usmAesCfb128Protocol elif v3_privProtocol_str == "usmAesCfb192Protocol": v3_privProtocol = cmdgen.usmAesCfb192Protocol elif v3_privProtocol_str == "usmAesCfb256Protocol": v3_privProtocol = cmdgen.usmAesCfb256Protocol elif v3_privProtocol_str == "usmNoPrivProtocol": v3_privProtocol = cmdgen.usmNoPrivProtocol else: v3_privProtocol = cmdgen.usmNoPrivProtocol return v3_privProtocol #read XML configuration passed from splunkd, need to refactor to support single instance mode def get_validation_config(): val_data = {} # read everything from stdin val_str = sys.stdin.read() # parse the validation XML doc = xml.dom.minidom.parseString(val_str) root = doc.documentElement item_node = root.getElementsByTagName("item")[0] if item_node: name = item_node.getAttribute("name") val_data["stanza"] = name params_node = item_node.getElementsByTagName("param") for param in params_node: name = param.getAttribute("name") if name and param.firstChild and \ param.firstChild.nodeType == param.firstChild.TEXT_NODE: val_data[name] = param.firstChild.data return val_data class ProcessCheckerThread(threading.Thread): def __init__(self, pid): threading.Thread.__init__(self) self.pid = pid def run(self): while(True): time.sleep(5) try: if os.name == 'nt': tasklist_response = subprocess.check_output('tasklist /FO csv /FI "PID eq '+str(self.pid)+'"') for line in tasklist_response.splitlines(): if str(line).find("No tasks are running"): raise OSError(errno.ESRCH) else: #doesn't kill , just checks if process is running on POSIX systems only os.kill(self.pid, 0) except OSError as error: if error.errno == errno.ESRCH: logger.info("Parent PID %s is not runnning , exiting." % self.pid) if child_pid is None: pass else: logger.info("Killing child process %s" % child_pid) if os.name == 'nt': os.kill(child_pid, signal.CTRL_C_EVENT) else: os.kill(child_pid, signal.SIGTERM) this_pid = os.getpid() logger.info("Killing this process %s" % this_pid) if os.name == 'nt': os.kill(this_pid, signal.CTRL_C_EVENT) else: os.kill(this_pid, signal.SIGTERM) if __name__ == '__main__': actual_execution = False if(sys.argv[-1] == "--redirected"): actual_execution = True if not actual_execution and len(sys.argv) > 1: if sys.argv[1] == "--scheme": do_scheme() sys.exit(0) elif sys.argv[1] == "--validate-arguments": do_validate() sys.exit(0) else: usage() else: config_str = sys.stdin.read() config = get_input_config(config_str) #generate a unique log name for each stanza. stanza_name = config.get("name") hash_of_stanza_name = hashlib.md5(stanza_name.encode('utf-8')) LOG_NAME = hash_of_stanza_name.hexdigest() + "_snmpmodinput_app_modularinput.log" LOG_FILENAME = os.path.join(SPLUNK_HOME,"var","log","splunk",LOG_NAME) global log_formatter,log_handler log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s stanza:{0}'.format(stanza_name)) log_handler = TimedRotatingFileHandler(LOG_FILENAME, when="d",interval=1,backupCount=5) log_handler.setFormatter(log_formatter) logger.addHandler(log_handler) run_process_checker=bool(int(config.get("run_process_checker",1))) global child_pid child_pid = None if run_process_checker: parent_pid = os.getppid() processCheckerThread = ProcessCheckerThread(parent_pid) logger.info("Starting process checker thread for parent PID %s " % parent_pid) processCheckerThread.start() if not actual_execution: use_system_python=bool(int(config.get("use_system_python",0))) system_python_path=config.get("system_python_path","/usr/bin/python") if use_system_python: script_args = [ system_python_path,sys.argv[0] ] #script_args.extend(sys.argv[1:]) script_args.append("--redirected") #cleanup up the Splunk python environment settings if 'LD_LIBRARY_PATH' in os.environ: del os.environ['LD_LIBRARY_PATH'] # Now we can run our command process = Popen(script_args,stdout=sys.stdout, stdin=PIPE, stderr=sys.stderr) child_pid = process.pid logger.info("Using System python at %s , child process opening with PID %s" % (system_python_path,child_pid)) stdout_value, stderr_value = process.communicate(input=config_str.encode()) process.wait() sys.exit(process.returncode) else: logger.info("Using Splunk's python") actual_execution = True if actual_execution: #change log level from configuration stanza if present log_level_str = config.get("log_level","INFO") log_level = logging.getLevelName(log_level_str) logger.setLevel(log_level) #enable pysnmp library debug logging if log_level_str == "DEBUG": debug_printer = Printer( logger=logger, handler=log_handler, formatter=log_formatter ) setLogger(Debug('io','dsp','msgproc','secmod','proxy',printer=debug_printer)) do_run(config) sys.exit(0)