from pysnmp.entity.rfc3413 import cmdrsp, context from pysnmp.proto import rfc1902 from pysnmp.carrier.asyncore.dispatch import AsyncoreDispatcher from pysnmp.carrier.asyncore.dgram import udp # Configuration parameters for each of SNMP Engines snmpEngineInfo = ( ('0102030405060708', udp.domainName + (0,), ('127.0.0.1', 161)), ('0807060504030201', udp.domainName + (1,), ('127.0.0.2', 161)) ) # Instantiate the single transport dispatcher object transportDispatcher = AsyncoreDispatcher() # Setup a custom data routing function to select snmpEngine by transportDomain transportDispatcher.registerRoutingCbFun(lambda td, t, d: td) # Instantiate and configure SNMP Engines for snmpEngineId, transportDomain, transportAddress in snmpEngineInfo: # Create SNMP engine with specific engineID snmpEngine = engine.SnmpEngine(rfc1902.OctetString(hexValue=snmpEngineId)) # Register SNMP Engine object with transport dispatcher. Request incoming # data from specific transport endpoint to be funneled to this SNMP Engine. snmpEngine.registerTransportDispatcher(transportDispatcher, transportDomain) # Transport setup # UDP over IPv4 config.addTransport( snmpEngine,
from pysnmp.entity.rfc3413 import cmdrsp, context from pysnmp.proto import rfc1902 from pysnmp.carrier.asyncore.dispatch import AsyncoreDispatcher from pysnmp.carrier.asyncore.dgram import udp # Configuration parameters for each of SNMP Engines snmpEngineInfo = (('0102030405060708', udp.domainName + (0, ), ('127.0.0.1', 161)), ('0807060504030201', udp.domainName + (1, ), ('127.0.0.2', 161))) # Instantiate the single transport dispatcher object transportDispatcher = AsyncoreDispatcher() # Setup a custom data routing function to select snmpEngine by transportDomain transportDispatcher.registerRoutingCbFun(lambda td, t, d: td) # Instantiate and configure SNMP Engines for snmpEngineId, transportDomain, transportAddress in snmpEngineInfo: # Create SNMP engine with specific engineID snmpEngine = engine.SnmpEngine(rfc1902.OctetString(hexValue=snmpEngineId)) # Register SNMP Engine object with transport dispatcher. Request incoming # data from specific transport endpoint to be funneled to this SNMP Engine. snmpEngine.registerTransportDispatcher(transportDispatcher, transportDomain) # Transport setup # UDP over IPv4 config.addTransport(snmpEngine, transportDomain,
errorStatus, errorIndex)) else: print('Notification %s for SNMP Engine %s delivered: ' % ( sendRequestHandle, snmpEngine.snmpEngineID.prettyPrint())) for name, val in varBinds: print('%s = %s' % (name.prettyPrint(), val.prettyPrint())) # Instantiate the single transport dispatcher object transportDispatcher = AsyncoreDispatcher() # Setup a custom data routing function to select snmpEngine by transportDomain transportDispatcher.registerRoutingCbFun( lambda td, ta, d: ta[1] % 3 and 'A' or 'B' ) snmpEngineA = SnmpEngine() snmpEngineA.registerTransportDispatcher(transportDispatcher, 'A') snmpEngineB = SnmpEngine() snmpEngineB.registerTransportDispatcher(transportDispatcher, 'B') for authData, transportTarget, contextData in TARGETS: # Pick one of the two SNMP engines snmpEngine = (transportTarget.getTransportInfo()[1][1] % 3 and snmpEngineA or snmpEngineB) sendPduHandle = sendNotification(
elif errorStatus: print('%s at %s' % (errorStatus.prettyPrint(), errorIndex and varBinds[int(errorIndex) - 1][0] or '?')) return True else: for varBind in varBinds: print(' = '.join([x.prettyPrint() for x in varBind])) # Instantiate the single transport dispatcher object transportDispatcher = AsyncoreDispatcher() # Setup a custom data routing function to select snmpEngine by transportDomain transportDispatcher.registerRoutingCbFun( lambda td, ta, d: ta[1] % 3 and 'A' or 'B') snmpEngineA = SnmpEngine() snmpEngineA.registerTransportDispatcher(transportDispatcher, 'A') snmpEngineB = SnmpEngine() snmpEngineB.registerTransportDispatcher(transportDispatcher, 'B') for authData, transportTarget, varBinds in TARGETS: snmpEngine = (transportTarget.getTransportInfo()[1][1] % 3 and snmpEngineA or snmpEngineB) getCmd(snmpEngine, authData, transportTarget, ContextData(),
def main(): class MibTreeProxyMixIn(object): MIB_INTRUMENTATION_CALL = None def _getMgmtFun(self, contextName): return self._routeToMibTree def _routeToMibTree(self, *varBinds, **context): cbFun = context['cbFun'] mibTreeReq = gCurrentRequestContext.copy() pdu = mibTreeReq['snmp-pdu'] pluginIdList = mibTreeReq['plugins-list'] logCtx = LogString(mibTreeReq) reqCtx = {} for pluginNum, pluginId in enumerate(pluginIdList): st, pdu = pluginManager.processCommandRequest( pluginId, snmpEngine, pdu, mibTreeReq, reqCtx) if st == status.BREAK: log.debug('plugin %s inhibits other plugins' % pluginId, ctx=logCtx) pluginIdList = pluginIdList[:pluginNum] break elif st == status.DROP: log.debug( 'received SNMP message, plugin %s muted request' % pluginId, ctx=logCtx) # TODO: need to report some special error to drop request cbFun(varBinds, **context) return elif st == status.RESPOND: log.debug( 'received SNMP message, plugin %s forced immediate response' % pluginId, ctx=logCtx) # TODO: should we respond something other than request? cbFun(varBinds, **context) return # Apply PDU to MIB(s) mibTreeId = mibTreeReq['mib-tree-id'] if not mibTreeId: log.error('no matching MIB tree route for the request', ctx=logCtx) cbFun(varBinds, **dict(context, error=smi_error.GenError())) return mibInstrum = mibTreeIdMap.get(mibTreeId) if not mibInstrum: log.error('MIB tree ID %s does not exist' % mibTreeId, ctx=logCtx) cbFun(varBinds, **dict(context, error=smi_error.GenError())) return log.debug('received SNMP message, applied on mib-tree-id %s' % mibTreeId, ctx=logCtx) cbCtx = pluginIdList, mibTreeId, mibTreeReq, snmpEngine, reqCtx, context[ 'cbFun'] mgmtFun = getattr(mibInstrum, self.MIB_INTRUMENTATION_CALL) mgmtFun( *varBinds, **dict(context, cbFun=self._mibTreeCbFun, cbCtx=cbCtx, acFun=None)) # TODO: it just occurred to me that `*varBinds` would look more consistent def _mibTreeCbFun(self, varBinds, **context): pluginIdList, mibTreeId, mibTreeReq, snmpEngine, reqCtx, cbFun = context[ 'cbCtx'] logCtx = LogString(mibTreeReq) err = context.get('error') if err: log.info('MIB operation resulted in error: %s' % err, ctx=logCtx) cbFun(varBinds, **dict(context, cbFun=cbFun)) # plugins need to work at var-binds level # # for key in tuple(mibTreeRsp): # pdu = mibTreeRsp['client-snmp-pdu'] # # for pluginId in pluginIdList: # st, pdu = pluginManager.processCommandResponse( # pluginId, snmpEngine, pdu, mibTreeReq, reqCtx # ) # # if st == status.BREAK: # log.debug('plugin %s inhibits other plugins' % pluginId, ctx=logCtx) # break # elif st == status.DROP: # log.debug('plugin %s muted response' % pluginId, ctx=logCtx) # self.releaseStateInformation(stateReference) # return # # try: # self.sendPdu(snmpEngine, stateReference, pdu) # # except PySnmpError: # log.error('mibTree message #%s, SNMP response error: %s' % (msgId, sys.exc_info()[1]), # ctx=logCtx) # # else: # log.debug('received mibTree message #%s, forwarded as SNMP message' % msgId, ctx=logCtx) class GetCommandResponder(MibTreeProxyMixIn, cmdrsp.GetCommandResponder): MIB_INTRUMENTATION_CALL = 'readMibObjects' class GetNextCommandResponder(MibTreeProxyMixIn, cmdrsp.NextCommandResponder): MIB_INTRUMENTATION_CALL = 'readNextMibObjects' class GetBulkCommandResponder(MibTreeProxyMixIn, cmdrsp.BulkCommandResponder): MIB_INTRUMENTATION_CALL = 'readNextMibObjects' class SetCommandResponder(MibTreeProxyMixIn, cmdrsp.SetCommandResponder): MIB_INTRUMENTATION_CALL = 'writeMibObjects' class LogString(LazyLogString): GROUPINGS = [ ['callflow-id'], [ 'snmp-engine-id', 'snmp-transport-domain', 'snmp-bind-address', 'snmp-bind-port', 'snmp-security-model', 'snmp-security-level', 'snmp-security-name', 'snmp-credentials-id' ], ['snmp-context-engine-id', 'snmp-context-name', 'snmp-context-id'], ['snmp-pdu', 'snmp-content-id'], ['snmp-peer-address', 'snmp-peer-port', 'snmp-peer-id'], ['mib-tree-id'], ['client-snmp-pdu'], ] FORMATTERS = { 'client-snmp-pdu': LazyLogString.prettyVarBinds, 'snmp-pdu': LazyLogString.prettyVarBinds, } def securityAuditObserver(snmpEngine, execpoint, variables, cbCtx): securityModel = variables.get('securityModel', 0) logMsg = 'SNMPv%s auth failure' % securityModel logMsg += ' at %s:%s' % variables['transportAddress'].getLocalAddress() logMsg += ' from %s:%s' % variables['transportAddress'] statusInformation = variables.get('statusInformation', {}) if securityModel in (1, 2): logMsg += ' using snmp-community-name "%s"' % statusInformation.get( 'communityName', '?') elif securityModel == 3: logMsg += ' using snmp-usm-user "%s"' % statusInformation.get( 'msgUserName', '?') try: logMsg += ': %s' % statusInformation['errorIndication'] except KeyError: pass log.error(logMsg) def usmRequestObserver(snmpEngine, execpoint, variables, cbCtx): mibTreeReq = {'snmp-security-engine-id': variables['securityEngineId']} cbCtx.clear() cbCtx.update(mibTreeReq) def requestObserver(snmpEngine, execpoint, variables, cbCtx): mibTreeReq = { 'callflow-id': '%10.10x' % random.randint(0, 0xffffffffff), 'snmp-engine-id': snmpEngine.snmpEngineID, 'snmp-transport-domain': variables['transportDomain'], 'snmp-peer-address': variables['transportAddress'][0], 'snmp-peer-port': variables['transportAddress'][1], 'snmp-bind-address': variables['transportAddress'].getLocalAddress()[0], 'snmp-bind-port': variables['transportAddress'].getLocalAddress()[1], 'snmp-security-model': variables['securityModel'], 'snmp-security-level': variables['securityLevel'], 'snmp-security-name': variables['securityName'], 'snmp-context-engine-id': variables['contextEngineId'], 'snmp-context-name': variables['contextName'], } try: mibTreeReq['snmp-security-engine-id'] = cbCtx.pop( 'snmp-security-engine-id') except KeyError: # SNMPv1/v2c mibTreeReq['snmp-security-engine-id'] = mibTreeReq[ 'snmp-engine-id'] mibTreeReq['snmp-credentials-id'] = macro.expandMacro( credIdMap.get( (str(snmpEngine.snmpEngineID), variables['transportDomain'], variables['securityModel'], variables['securityLevel'], str(variables['securityName']))), mibTreeReq) k = '#'.join([ str(x) for x in (variables['contextEngineId'], variables['contextName']) ]) for x, y in contextIdList: if y.match(k): mibTreeReq['snmp-context-id'] = macro.expandMacro( x, mibTreeReq) break else: mibTreeReq['snmp-context-id'] = None addr = '%s:%s#%s:%s' % ( variables['transportAddress'][0], variables['transportAddress'][1], variables['transportAddress'].getLocalAddress()[0], variables['transportAddress'].getLocalAddress()[1]) for pat, peerId in peerIdMap.get(str(variables['transportDomain']), ()): if pat.match(addr): mibTreeReq['snmp-peer-id'] = macro.expandMacro( peerId, mibTreeReq) break else: mibTreeReq['snmp-peer-id'] = None pdu = variables['pdu'] k = '#'.join([ snmpPduTypesMap.get(variables['pdu'].tagSet, '?'), '|'.join([str(x[0]) for x in v2c.apiPDU.getVarBinds(pdu)]) ]) for x, y in contentIdList: if y.match(k): mibTreeReq['snmp-content-id'] = macro.expandMacro( x, mibTreeReq) break else: mibTreeReq['snmp-content-id'] = None mibTreeReq['plugins-list'] = pluginIdMap.get( (mibTreeReq['snmp-credentials-id'], mibTreeReq['snmp-context-id'], mibTreeReq['snmp-peer-id'], mibTreeReq['snmp-content-id']), []) mibTreeReq['mib-tree-id'] = routingMap.get( (mibTreeReq['snmp-credentials-id'], mibTreeReq['snmp-context-id'], mibTreeReq['snmp-peer-id'], mibTreeReq['snmp-content-id'])) mibTreeReq['snmp-pdu'] = pdu cbCtx.clear() cbCtx.update(mibTreeReq) # # main script starts here # helpMessage = """\ Usage: %s [--help] [--version ] [--debug-snmp=<%s>] [--debug-asn1=<%s>] [--daemonize] [--process-user=<uname>] [--process-group=<gname>] [--pid-file=<file>] [--logging-method=<%s[:args>]>] [--log-level=<%s>] [--config-file=<file>]""" % (sys.argv[0], '|'.join([ x for x in getattr(pysnmp_debug, 'FLAG_MAP', getattr(pysnmp_debug, 'flagMap', ())) if x != 'mibview' ]), '|'.join([ x for x in getattr(pyasn1_debug, 'FLAG_MAP', getattr(pyasn1_debug, 'flagMap', ())) ]), '|'.join(log.methodsMap), '|'.join(log.levelsMap)) try: opts, params = getopt.getopt(sys.argv[1:], 'hv', [ 'help', 'version', 'debug=', 'debug-snmp=', 'debug-asn1=', 'daemonize', 'process-user='******'process-group=', 'pid-file=', 'logging-method=', 'log-level=', 'config-file=' ]) except Exception: sys.stderr.write('ERROR: %s\r\n%s\r\n' % (sys.exc_info()[1], helpMessage)) return if params: sys.stderr.write('ERROR: extra arguments supplied %s\r\n%s\r\n' % (params, helpMessage)) return pidFile = '' cfgFile = CONFIG_FILE foregroundFlag = True procUser = procGroup = None loggingMethod = ['stderr'] loggingLevel = None for opt in opts: if opt[0] == '-h' or opt[0] == '--help': sys.stderr.write("""\ Synopsis: SNMP Command Responder. Runs one or more SNMP command responders (agents) and one or more trees of MIB objects representing SNMP-managed entities. The tool applies received messages onto one of the MIB trees chosen by tool's configuration. Documentation: http://snmplabs.com/snmpresponder/ %s """ % helpMessage) return if opt[0] == '-v' or opt[0] == '--version': import snmpresponder import pysnmp import pyasn1 sys.stderr.write("""\ SNMP Command Responder version %s, written by Ilya Etingof <*****@*****.**> Using foundation libraries: pysnmp %s, pyasn1 %s. Python interpreter: %s Software documentation and support at http://snmplabs.com/snmpresponder/ %s """ % (snmpresponder.__version__, hasattr(pysnmp, '__version__') and pysnmp.__version__ or 'unknown', hasattr(pyasn1, '__version__') and pyasn1.__version__ or 'unknown', sys.version, helpMessage)) return elif opt[0] == '--debug-snmp': pysnmp_debug.setLogger( pysnmp_debug.Debug(*opt[1].split(','), loggerName=PROGRAM_NAME + '.pysnmp')) elif opt[0] == '--debug-asn1': pyasn1_debug.setLogger( pyasn1_debug.Debug(*opt[1].split(','), loggerName=PROGRAM_NAME + '.pyasn1')) elif opt[0] == '--daemonize': foregroundFlag = False elif opt[0] == '--process-user': procUser = opt[1] elif opt[0] == '--process-group': procGroup = opt[1] elif opt[0] == '--pid-file': pidFile = opt[1] elif opt[0] == '--logging-method': loggingMethod = opt[1].split(':') elif opt[0] == '--log-level': loggingLevel = opt[1] elif opt[0] == '--config-file': cfgFile = opt[1] with daemon.PrivilegesOf(procUser, procGroup): try: log.setLogger(PROGRAM_NAME, *loggingMethod, force=True) if loggingLevel: log.setLevel(loggingLevel) except SnmpResponderError: sys.stderr.write('%s\r\n%s\r\n' % (sys.exc_info()[1], helpMessage)) return try: cfgTree = cparser.Config().load(cfgFile) except SnmpResponderError: log.error('configuration parsing error: %s' % sys.exc_info()[1]) return if cfgTree.getAttrValue('program-name', '', default=None) != PROGRAM_NAME: log.error('config file %s does not match program name %s' % (cfgFile, PROGRAM_NAME)) return if cfgTree.getAttrValue('config-version', '', default=None) != CONFIG_VERSION: log.error( 'config file %s version is not compatible with program version %s' % (cfgFile, CONFIG_VERSION)) return random.seed() gCurrentRequestContext = {} credIdMap = {} peerIdMap = {} contextIdList = [] contentIdList = [] pluginIdMap = {} routingMap = {} mibTreeIdMap = {} engineIdMap = {} transportDispatcher = AsyncoreDispatcher() transportDispatcher.registerRoutingCbFun(lambda td, t, d: td) transportDispatcher.setSocketMap() # use global asyncore socket map # # Initialize plugin modules # pluginManager = PluginManager(macro.expandMacros( cfgTree.getAttrValue('plugin-modules-path-list', '', default=[], vector=True), {'config-dir': os.path.dirname(cfgFile)}), progId=PROGRAM_NAME, apiVer=PLUGIN_API_VERSION) for pluginCfgPath in cfgTree.getPathsToAttr('plugin-id'): pluginId = cfgTree.getAttrValue('plugin-id', *pluginCfgPath) pluginMod = cfgTree.getAttrValue('plugin-module', *pluginCfgPath) pluginOptions = macro.expandMacros( cfgTree.getAttrValue('plugin-options', *pluginCfgPath, default=[], vector=True), {'config-dir': os.path.dirname(cfgFile)}) log.info( 'configuring plugin ID %s (at %s) from module %s with options %s...' % (pluginId, '.'.join(pluginCfgPath), pluginMod, ', '.join(pluginOptions) or '<none>')) with daemon.PrivilegesOf(procUser, procGroup): try: pluginManager.loadPlugin(pluginId, pluginMod, pluginOptions) except SnmpResponderError: log.error('plugin %s not loaded: %s' % (pluginId, sys.exc_info()[1])) return for configEntryPath in cfgTree.getPathsToAttr('snmp-credentials-id'): credId = cfgTree.getAttrValue('snmp-credentials-id', *configEntryPath) configKey = [] log.info('configuring snmp-credentials %s (at %s)...' % (credId, '.'.join(configEntryPath))) engineId = cfgTree.getAttrValue('snmp-engine-id', *configEntryPath) if engineId in engineIdMap: snmpEngine, snmpContext, snmpEngineMap = engineIdMap[engineId] log.info('using engine-id %s' % snmpEngine.snmpEngineID.prettyPrint()) else: snmpEngine = engine.SnmpEngine(snmpEngineID=engineId) snmpContext = context.SnmpContext(snmpEngine) snmpEngineMap = {'transportDomain': {}, 'securityName': {}} snmpEngine.observer.registerObserver( securityAuditObserver, 'rfc2576.prepareDataElements:sm-failure', 'rfc3412.prepareDataElements:sm-failure', cbCtx=gCurrentRequestContext) snmpEngine.observer.registerObserver( requestObserver, 'rfc3412.receiveMessage:request', cbCtx=gCurrentRequestContext) snmpEngine.observer.registerObserver(usmRequestObserver, 'rfc3414.processIncomingMsg', cbCtx=gCurrentRequestContext) GetCommandResponder(snmpEngine, snmpContext) GetNextCommandResponder(snmpEngine, snmpContext) GetBulkCommandResponder(snmpEngine, snmpContext) SetCommandResponder(snmpEngine, snmpContext) engineIdMap[engineId] = snmpEngine, snmpContext, snmpEngineMap log.info('new engine-id %s' % snmpEngine.snmpEngineID.prettyPrint()) configKey.append(str(snmpEngine.snmpEngineID)) transportDomain = cfgTree.getAttrValue('snmp-transport-domain', *configEntryPath) transportDomain = rfc1902.ObjectName(transportDomain) if (transportDomain[:len(udp.DOMAIN_NAME)] != udp.DOMAIN_NAME and udp6 and transportDomain[:len(udp6.DOMAIN_NAME)] != udp6.DOMAIN_NAME): log.error('unknown transport domain %s' % (transportDomain, )) return if transportDomain in snmpEngineMap['transportDomain']: bindAddr, transportDomain = snmpEngineMap['transportDomain'][ transportDomain] log.info('using transport endpoint [%s]:%s, transport ID %s' % (bindAddr[0], bindAddr[1], transportDomain)) else: bindAddr = cfgTree.getAttrValue('snmp-bind-address', *configEntryPath) transportOptions = cfgTree.getAttrValue('snmp-transport-options', *configEntryPath, default=[], vector=True) try: bindAddr, bindAddrMacro = endpoint.parseTransportAddress( transportDomain, bindAddr, transportOptions) except SnmpResponderError: log.error('bad snmp-bind-address specification %s at %s' % (bindAddr, '.'.join(configEntryPath))) return if transportDomain[:len(udp.DOMAIN_NAME)] == udp.DOMAIN_NAME: transport = udp.UdpTransport() else: transport = udp6.Udp6Transport() t = transport.openServerMode(bindAddr) if 'transparent-proxy' in transportOptions: t.enablePktInfo() t.enableTransparent() elif 'virtual-interface' in transportOptions: t.enablePktInfo() snmpEngine.registerTransportDispatcher(transportDispatcher, transportDomain) config.addSocketTransport(snmpEngine, transportDomain, t) snmpEngineMap['transportDomain'][ transportDomain] = bindAddr, transportDomain log.info( 'new transport endpoint [%s]:%s, options %s, transport ID %s' % (bindAddr[0], bindAddr[1], transportOptions and '/'.join(transportOptions) or '<none>', transportDomain)) configKey.append(transportDomain) securityModel = cfgTree.getAttrValue('snmp-security-model', *configEntryPath) securityModel = rfc1902.Integer(securityModel) securityLevel = cfgTree.getAttrValue('snmp-security-level', *configEntryPath) securityLevel = rfc1902.Integer(securityLevel) securityName = cfgTree.getAttrValue('snmp-security-name', *configEntryPath) if securityModel in (1, 2): if securityName in snmpEngineMap['securityName']: if snmpEngineMap['securityName'][ securityModel] == securityModel: log.info('using security-name %s' % securityName) else: raise SnmpResponderError( 'snmp-security-name %s already in use at snmp-security-model %s' % (securityName, securityModel)) else: communityName = cfgTree.getAttrValue('snmp-community-name', *configEntryPath) config.addV1System(snmpEngine, securityName, communityName, securityName=securityName) log.info( 'new community-name %s, security-model %s, security-name %s, security-level %s' % (communityName, securityModel, securityName, securityLevel)) snmpEngineMap['securityName'][securityName] = securityModel configKey.append(securityModel) configKey.append(securityLevel) configKey.append(securityName) elif securityModel == 3: if securityName in snmpEngineMap['securityName']: log.info('using USM security-name: %s' % securityName) else: usmUser = cfgTree.getAttrValue('snmp-usm-user', *configEntryPath) securityEngineId = cfgTree.getAttrValue( 'snmp-security-engine-id', *configEntryPath, default=None) if securityEngineId: securityEngineId = rfc1902.OctetString(securityEngineId) log.info( 'new USM user %s, security-model %s, security-level %s, ' 'security-name %s, security-engine-id %s' % (usmUser, securityModel, securityLevel, securityName, securityEngineId and securityEngineId.prettyPrint() or '<none>')) if securityLevel in (2, 3): usmAuthProto = cfgTree.getAttrValue( 'snmp-usm-auth-protocol', *configEntryPath, default=config.USM_AUTH_HMAC96_MD5) try: usmAuthProto = authProtocols[usmAuthProto.upper()] except KeyError: pass usmAuthProto = rfc1902.ObjectName(usmAuthProto) usmAuthKey = cfgTree.getAttrValue('snmp-usm-auth-key', *configEntryPath) log.info( 'new USM authentication key: %s, authentication protocol: %s' % (usmAuthKey, usmAuthProto)) if securityLevel == 3: usmPrivProto = cfgTree.getAttrValue( 'snmp-usm-priv-protocol', *configEntryPath, default=config.USM_PRIV_CBC56_DES) try: usmPrivProto = privProtocols[usmPrivProto.upper()] except KeyError: pass usmPrivProto = rfc1902.ObjectName(usmPrivProto) usmPrivKey = cfgTree.getAttrValue('snmp-usm-priv-key', *configEntryPath, default=None) log.info( 'new USM encryption key: %s, encryption protocol: %s' % (usmPrivKey, usmPrivProto)) config.addV3User(snmpEngine, usmUser, usmAuthProto, usmAuthKey, usmPrivProto, usmPrivKey, securityEngineId=securityEngineId) else: config.addV3User(snmpEngine, usmUser, usmAuthProto, usmAuthKey, securityEngineId=securityEngineId) else: config.addV3User(snmpEngine, usmUser, securityEngineId=securityEngineId) snmpEngineMap['securityName'][securityName] = securityModel configKey.append(securityModel) configKey.append(securityLevel) configKey.append(securityName) else: raise SnmpResponderError('unknown snmp-security-model: %s' % securityModel) configKey = tuple(configKey) if configKey in credIdMap: log.error( 'ambiguous configuration for key snmp-credentials-id=%s at %s' % (credId, '.'.join(configEntryPath))) return credIdMap[configKey] = credId duplicates = {} for peerCfgPath in cfgTree.getPathsToAttr('snmp-peer-id'): peerId = cfgTree.getAttrValue('snmp-peer-id', *peerCfgPath) if peerId in duplicates: log.error( 'duplicate snmp-peer-id=%s at %s and %s' % (peerId, '.'.join(peerCfgPath), '.'.join(duplicates[peerId]))) return duplicates[peerId] = peerCfgPath log.info('configuring peer ID %s (at %s)...' % (peerId, '.'.join(peerCfgPath))) transportDomain = cfgTree.getAttrValue('snmp-transport-domain', *peerCfgPath) if transportDomain not in peerIdMap: peerIdMap[transportDomain] = [] for peerAddress in cfgTree.getAttrValue( 'snmp-peer-address-pattern-list', *peerCfgPath, vector=True): for bindAddress in cfgTree.getAttrValue( 'snmp-bind-address-pattern-list', *peerCfgPath, vector=True): peerIdMap[transportDomain].append( (re.compile(peerAddress + '#' + bindAddress), peerId)) duplicates = {} for contextCfgPath in cfgTree.getPathsToAttr('snmp-context-id'): contextId = cfgTree.getAttrValue('snmp-context-id', *contextCfgPath) if contextId in duplicates: log.error('duplicate snmp-context-id=%s at %s and %s' % (contextId, '.'.join(contextCfgPath), '.'.join( duplicates[contextId]))) return duplicates[contextId] = contextCfgPath k = '#'.join((cfgTree.getAttrValue('snmp-context-engine-id-pattern', *contextCfgPath), cfgTree.getAttrValue('snmp-context-name-pattern', *contextCfgPath))) log.info('configuring context ID %s (at %s), composite key: %s' % (contextId, '.'.join(contextCfgPath), k)) contextIdList.append((contextId, re.compile(k))) duplicates = {} for contentCfgPath in cfgTree.getPathsToAttr('snmp-content-id'): contentId = cfgTree.getAttrValue('snmp-content-id', *contentCfgPath) if contentId in duplicates: log.error('duplicate snmp-content-id=%s at %s and %s' % (contentId, '.'.join(contentCfgPath), '.'.join( duplicates[contentId]))) return duplicates[contentId] = contentCfgPath for x in cfgTree.getAttrValue('snmp-pdu-oid-prefix-pattern-list', *contentCfgPath, vector=True): k = '#'.join([ cfgTree.getAttrValue('snmp-pdu-type-pattern', *contentCfgPath), x ]) log.info('configuring content ID %s (at %s), composite key: %s' % (contentId, '.'.join(contentCfgPath), k)) contentIdList.append((contentId, re.compile(k))) del duplicates for pluginCfgPath in cfgTree.getPathsToAttr('using-plugin-id-list'): pluginIdList = cfgTree.getAttrValue('using-plugin-id-list', *pluginCfgPath, vector=True) log.info('configuring plugin ID(s) %s (at %s)...' % (','.join(pluginIdList), '.'.join(pluginCfgPath))) for credId in cfgTree.getAttrValue('matching-snmp-credentials-id-list', *pluginCfgPath, vector=True): for peerId in cfgTree.getAttrValue('matching-snmp-peer-id-list', *pluginCfgPath, vector=True): for contextId in cfgTree.getAttrValue( 'matching-snmp-context-id-list', *pluginCfgPath, vector=True): for contentId in cfgTree.getAttrValue( 'matching-snmp-content-id-list', *pluginCfgPath, vector=True): k = credId, contextId, peerId, contentId if k in pluginIdMap: log.error( 'duplicate snmp-credentials-id %s, snmp-context-id %s, snmp-peer-id %s, snmp-content-id %s at plugin-id(s) %s' % (credId, contextId, peerId, contentId, ','.join(pluginIdList))) return else: log.info( 'configuring plugin(s) %s (at %s), composite key: %s' % (','.join(pluginIdList), '.'.join(pluginCfgPath), '/'.join(k))) for pluginId in pluginIdList: if not pluginManager.hasPlugin(pluginId): log.error( 'undefined plugin ID %s referenced at %s' % (pluginId, '.'.join(pluginCfgPath))) return pluginIdMap[k] = pluginIdList for routeCfgPath in cfgTree.getPathsToAttr('using-mib-tree-id'): mibTreeId = cfgTree.getAttrValue('using-mib-tree-id', *routeCfgPath) log.info('configuring destination MIB tree ID(s) %s (at %s)...' % (mibTreeId, '.'.join(routeCfgPath))) for credId in cfgTree.getAttrValue('matching-snmp-credentials-id-list', *routeCfgPath, vector=True): for peerId in cfgTree.getAttrValue('matching-snmp-peer-id-list', *routeCfgPath, vector=True): for contextId in cfgTree.getAttrValue( 'matching-snmp-context-id-list', *routeCfgPath, vector=True): for contentId in cfgTree.getAttrValue( 'matching-snmp-content-id-list', *routeCfgPath, vector=True): k = credId, contextId, peerId, contentId if k in routingMap: log.error( 'duplicate snmp-credentials-id %s, snmp-context-id %s, snmp-peer-id %s, snmp-content-id %s at mib-tree-id(s) %s' % (credId, contextId, peerId, contentId, ','.join(mibTreeIdList))) return else: routingMap[k] = mibTreeId log.info( 'configuring MIB tree routing to %s (at %s), composite key: %s' % (mibTreeId, '.'.join(routeCfgPath), '/'.join(k))) for mibTreeCfgPath in cfgTree.getPathsToAttr('mib-tree-id'): mibTreeId = cfgTree.getAttrValue('mib-tree-id', *mibTreeCfgPath) log.info('configuring MIB tree ID %s (at %s)...' % (mibTreeId, '.'.join(mibTreeCfgPath))) mibTextPaths = cfgTree.getAttrValue('mib-text-search-path-list', *mibTreeCfgPath, default=[], vector=True) mibCodePatternPaths = macro.expandMacros( cfgTree.getAttrValue('mib-code-modules-pattern-list', *mibTreeCfgPath, default=[], vector=True), {'config-dir': os.path.dirname(cfgFile)}) mibBuilder = builder.MibBuilder() compiler.addMibCompiler(mibBuilder, sources=mibTextPaths) for topDir in mibCodePatternPaths: filenameRegExp = re.compile(os.path.basename(topDir)) topDir = os.path.dirname(topDir) for root, dirs, files in os.walk(topDir): if not files or root.endswith('__pycache__'): continue mibBuilder.setMibSources(builder.DirMibSource(root), *mibBuilder.getMibSources()) for filename in files: if not filenameRegExp.match(filename): log.debug( 'skipping non-matching file %s while loading ' 'MIB tree ID %s' % (filename, mibTreeId)) continue module, _ = os.path.splitext(filename) try: mibBuilder.loadModule(module) except PySnmpError as ex: log.error('fail to load MIB implementation from file ' '%s into MIB tree ID %s' % (os.path.join(root, filename), mibTreeId)) raise SnmpResponderError(str(ex)) log.info('loaded MIB implementation file %s into MIB tree ' 'ID %s' % (os.path.join(root, filename), mibTreeId)) mibCodePackages = macro.expandMacros( cfgTree.getAttrValue('mib-code-packages-pattern-list', *mibTreeCfgPath, default=[], vector=True), {'config-dir': os.path.dirname(cfgFile)}) for mibCodePackage in mibCodePackages: mibCodePackageRegExp = re.compile(mibCodePackage) for entryPoint in pkg_resources.iter_entry_points( 'snmpresponder.mibs'): log.debug('found extension entry point %s' % entryPoint.name) mibPackage = entryPoint.load() root = os.path.dirname(mibPackage.__file__) mibPathSet = False for filename in os.listdir(root): if filename.startswith('__init__'): continue if not os.path.isfile(os.path.join(root, filename)): continue mibPath = '.'.join((entryPoint.name, filename)) if not mibCodePackageRegExp.match(mibPath): log.debug( 'extension MIB %s from %s is NOT configured, ' 'skipping' % (mibPath, entryPoint.name)) continue if not mibPathSet: mibBuilder.setMibSources(builder.DirMibSource(root), *mibBuilder.getMibSources()) mibPathSet = True log.debug('loading extension MIB %s from %s into MIB tree ' 'ID %s' % (mibPath, entryPoint.name, mibTreeId)) module, _ = os.path.splitext(filename) try: mibBuilder.loadModule(module) except PySnmpError as ex: log.error('fail to load MIB implementation %s from ' '%s into MIB tree ID %s' % (mibPath, entryPoint.name, mibTreeId)) raise SnmpResponderError(str(ex)) log.info( 'loaded MIB implementation %s from %s into MIB tree ' 'ID %s' % (mibPath, entryPoint.name, mibTreeId)) mibTreeIdMap[mibTreeId] = instrum.MibInstrumController(mibBuilder) log.info('loaded new MIB tree ID %s' % mibTreeId) if not foregroundFlag: try: daemon.daemonize(pidFile) except Exception: log.error('can not daemonize process: %s' % sys.exc_info()[1]) return # Run mainloop log.info('starting I/O engine...') transportDispatcher.jobStarted(1) # server job would never finish with daemon.PrivilegesOf(procUser, procGroup, final=True): while True: try: transportDispatcher.runDispatcher() except (PySnmpError, SnmpResponderError, socket.error): log.error(str(sys.exc_info()[1])) continue except Exception: transportDispatcher.closeDispatcher() raise
def main(): parser = argparse.ArgumentParser(add_help=False) parser.add_argument( '-v', '--version', action='version', version=utils.TITLE) parser.add_argument( '-h', action='store_true', dest='usage', help='Brief usage message') parser.add_argument( '--help', action='store_true', help='Detailed help message') parser.add_argument( '--quiet', action='store_true', help='Do not print out informational messages') parser.add_argument( '--debug', choices=pysnmp_debug.flagMap, action='append', type=str, default=[], help='Enable one or more categories of SNMP debugging.') parser.add_argument( '--debug-asn1', choices=pyasn1_debug.FLAG_MAP, action='append', type=str, default=[], help='Enable one or more categories of ASN.1 debugging.') parser.add_argument( '--logging-method', type=lambda x: x.split(':'), metavar='=<%s[:args]>]' % '|'.join(log.METHODS_MAP), default='stderr', help='Logging method.') parser.add_argument( '--log-level', choices=log.LEVELS_MAP, type=str, default='info', help='Logging level.') parser.add_argument( '--reporting-method', type=lambda x: x.split(':'), metavar='=<%s[:args]>]' % '|'.join(ReportingManager.REPORTERS), default='null', help='Activity metrics reporting method.') parser.add_argument( '--daemonize', action='store_true', help='Disengage from controlling terminal and become a daemon') parser.add_argument( '--process-user', type=str, help='If run as root, switch simulator daemon to this user right ' 'upon binding privileged ports') parser.add_argument( '--process-group', type=str, help='If run as root, switch simulator daemon to this group right ' 'upon binding privileged ports') parser.add_argument( '--pid-file', metavar='<FILE>', type=str, default='/var/run/%s/%s.pid' % (__name__, os.getpid()), help='SNMP simulation data file to write records to') parser.add_argument( '--cache-dir', metavar='<DIR>', type=str, help='Location for SNMP simulation data file indices to create') parser.add_argument( '--force-index-rebuild', action='store_true', help='Rebuild simulation data files indices even if they seem ' 'up to date') parser.add_argument( '--validate-data', action='store_true', help='Validate simulation data files on daemon start-up') parser.add_argument( '--variation-modules-dir', metavar='<DIR>', type=str, action='append', default=[], help='Variation modules search path(s)') parser.add_argument( '--variation-module-options', metavar='<module[=alias][:args]>', type=str, action='append', default=[], help='Options for a specific variation module') parser.add_argument( '--v3-only', action='store_true', help='Trip legacy SNMP v1/v2c support to gain a little lesser memory ' 'footprint') parser.add_argument( '--transport-id-offset', type=int, default=0, help='Start numbering the last sub-OID of transport endpoint OIDs ' 'starting from this ID') parser.add_argument( '--max-var-binds', type=int, default=64, help='Maximum number of variable bindings to include in a single ' 'response') parser.add_argument( '--args-from-file', metavar='<FILE>', type=str, help='Read SNMP engine(s) command-line configuration from this ' 'file. Can be useful when command-line is too long') # We do not parse SNMP params with argparse, but we want its -h/--help snmp_helper = argparse.ArgumentParser( description=DESCRIPTION, add_help=False, parents=[parser]) v3_usage = """\ Configure one or more independent SNMP engines. Each SNMP engine has a distinct engine ID, its own set of SNMP USM users, one or more network transport endpoints to listen on and its own simulation data directory. Each SNMP engine configuration starts with `--v3-engine-id <arg>` parameter followed by other configuration options up to the next `--v3-engine-id` option or end of command line Example ------- $ snmp-command-responder \\ --v3-engine-id auto \\ --data-dir ./data --agent-udpv4-endpoint=127.0.0.1:1024 \\ --v3-engine-id auto \\ --data-dir ./data --agent-udpv4-endpoint=127.0.0.1:1025 \\ --data-dir ./data --agent-udpv4-endpoint=127.0.0.1:1026 Besides network endpoints, simulated agents can be addressed by SNMPv1/v2c community name or SNMPv3 context engine ID/name. These parameters are configured automatically based on simulation data file paths relative to `--data-dir`. """ v3_group = snmp_helper.add_argument_group(v3_usage) v3_group.add_argument( '--v3-engine-id', type=str, metavar='<HEX|auto>', default='auto', help='SNMPv3 engine ID') v3_group.add_argument( '--v3-user', metavar='<STRING>', type=functools.partial(_parse_sized_string, min_length=1), help='SNMPv3 USM user (security) name') v3_group.add_argument( '--v3-auth-key', type=_parse_sized_string, help='SNMPv3 USM authentication key (must be > 8 chars)') v3_group.add_argument( '--v3-auth-proto', choices=AUTH_PROTOCOLS, type=lambda x: x.upper(), default='NONE', help='SNMPv3 USM authentication protocol') v3_group.add_argument( '--v3-priv-key', type=_parse_sized_string, help='SNMPv3 USM privacy (encryption) key (must be > 8 chars)') v3_group.add_argument( '--v3-priv-proto', choices=PRIV_PROTOCOLS, type=lambda x: x.upper(), default='NONE', help='SNMPv3 USM privacy (encryption) protocol') v3_group.add_argument( '--v3-context-engine-id', type=lambda x: univ.OctetString(hexValue=x[2:]), help='SNMPv3 context engine ID') v3_group.add_argument( '--v3-context-name', type=str, default='', help='SNMPv3 context engine ID') v3_group.add_argument( '--agent-udpv4-endpoint', type=endpoints.parse_endpoint, metavar='<[X.X.X.X]:NNNNN>', help='SNMP agent UDP/IPv4 address to listen on (name:port)') v3_group.add_argument( '--agent-udpv6-endpoint', type=functools.partial(endpoints.parse_endpoint, ipv6=True), metavar='<[X:X:..X]:NNNNN>', help='SNMP agent UDP/IPv6 address to listen on ([name]:port)') v3_group.add_argument( '--data-dir', type=str, metavar='<DIR>', help='SNMP simulation data recordings directory.') args, unparsed_args = parser.parse_known_args() if args.usage: snmp_helper.print_usage(sys.stderr) return 1 if args.help: snmp_helper.print_help(sys.stderr) return 1 _, unknown_args = snmp_helper.parse_known_args(unparsed_args) if unknown_args: sys.stderr.write( 'ERROR: Unknown command-line parameter(s) ' '%s\r\n' % ' '.join(unknown_args)) snmp_helper.print_usage(sys.stderr) return 1 # Reformat unparsed args into a list of (option, value) tuples snmp_args = [] name = None for opt in unparsed_args: if '=' in opt: snmp_args.append(opt.split('=')) elif name: snmp_args.append((name, opt)) name = None else: name = opt if name: sys.stderr.write( 'ERROR: Non-paired command-line key-value parameter ' '%s\r\n' % name) snmp_helper.print_usage(sys.stderr) return 1 if args.cache_dir: confdir.cache = args.cache_dir if args.variation_modules_dir: confdir.variation = args.variation_modules_dir variation_modules_options = variation.parse_modules_options( args.variation_module_options) if args.args_from_file: try: with open(args.args_from_file) as fl: snmp_args.extend([handler.split('=', 1) for handler in fl.read().split()]) except Exception as exc: sys.stderr.write( 'ERROR: file %s opening failure: ' '%s\r\n' % (args.args_from_file, exc)) snmp_helper.print_usage(sys.stderr) return 1 with daemon.PrivilegesOf(args.process_user, args.process_group): proc_name = os.path.basename(sys.argv[0]) try: log.set_logger(proc_name, *args.logging_method, force=True) if args.log_level: log.set_level(args.log_level) except SnmpsimError as exc: sys.stderr.write('%s\r\n' % exc) snmp_helper.print_usage(sys.stderr) return 1 try: ReportingManager.configure(*args.reporting_method) except SnmpsimError as exc: sys.stderr.write('%s\r\n' % exc) snmp_helper.print_usage(sys.stderr) return 1 if args.daemonize: try: daemon.daemonize(args.pid_file) except Exception as exc: sys.stderr.write( 'ERROR: cant daemonize process: %s\r\n' % exc) snmp_helper.print_usage(sys.stderr) return 1 if not os.path.exists(confdir.cache): try: with daemon.PrivilegesOf(args.process_user, args.process_group): os.makedirs(confdir.cache) except OSError as exc: log.error('failed to create cache directory "%s": ' '%s' % (confdir.cache, exc)) return 1 else: log.info('Cache directory "%s" created' % confdir.cache) variation_modules = variation.load_variation_modules( confdir.variation, variation_modules_options) with daemon.PrivilegesOf(args.process_user, args.process_group): variation.initialize_variation_modules( variation_modules, mode='variating') def configure_managed_objects( data_dirs, data_index_instrum_controller, snmp_engine=None, snmp_context=None): """Build pysnmp Managed Objects base from data files information""" _mib_instrums = {} _data_files = {} for dataDir in data_dirs: log.info( 'Scanning "%s" directory for %s data ' 'files...' % (dataDir, ','.join( [' *%s%s' % (os.path.extsep, x.ext) for x in variation.RECORD_TYPES.values()]))) if not os.path.exists(dataDir): log.info('Directory "%s" does not exist' % dataDir) continue log.msg.inc_ident() for (full_path, text_parser, community_name) in datafile.get_data_files(dataDir): if community_name in _data_files: log.error( 'ignoring duplicate Community/ContextName "%s" for data ' 'file %s (%s already loaded)' % (community_name, full_path, _data_files[community_name])) continue elif full_path in _mib_instrums: mib_instrum = _mib_instrums[full_path] log.info('Configuring *shared* %s' % (mib_instrum,)) else: data_file = datafile.DataFile( full_path, text_parser, variation_modules) data_file.index_text(args.force_index_rebuild, args.validate_data) MibController = controller.MIB_CONTROLLERS[data_file.layout] mib_instrum = MibController(data_file) _mib_instrums[full_path] = mib_instrum _data_files[community_name] = full_path log.info('Configuring %s' % (mib_instrum,)) log.info('SNMPv1/2c community name: %s' % (community_name,)) agent_name = md5( univ.OctetString(community_name).asOctets()).hexdigest() context_name = agent_name if not args.v3_only: # snmpCommunityTable::snmpCommunityIndex can't be > 32 config.addV1System( snmp_engine, agent_name, community_name, contextName=context_name) snmp_context.registerContextName(context_name, mib_instrum) if len(community_name) <= 32: snmp_context.registerContextName(community_name, mib_instrum) data_index_instrum_controller.add_data_file( full_path, community_name, context_name) log.info( 'SNMPv3 Context Name: %s' '%s' % (context_name, len(community_name) <= 32 and ' or %s' % community_name or '')) log.msg.dec_ident() del _mib_instrums del _data_files # Bind transport endpoints for idx, opt in enumerate(snmp_args): if opt[0] == '--agent-udpv4-endpoint': snmp_args[idx] = ( opt[0], endpoints.IPv4TransportEndpoints().add(opt[1])) elif opt[0] == '--agent-udpv6-endpoint': snmp_args[idx] = ( opt[0], endpoints.IPv6TransportEndpoints().add(opt[1])) # Start configuring SNMP engine(s) transport_dispatcher = AsyncoreDispatcher() transport_dispatcher.registerRoutingCbFun(lambda td, t, d: td) if not snmp_args or snmp_args[0][0] != '--v3-engine-id': snmp_args.insert(0, ('--v3-engine-id', 'auto')) if snmp_args and snmp_args[-1][0] != 'end-of-options': snmp_args.append(('end-of-options', '')) snmp_engine = None transport_index = { 'udpv4': args.transport_id_offset, 'udpv6': args.transport_id_offset, } for opt in snmp_args: if opt[0] in ('--v3-engine-id', 'end-of-options'): if snmp_engine: log.info('--- SNMP Engine configuration') log.info( 'SNMPv3 EngineID: ' '%s' % (hasattr(snmp_engine, 'snmpEngineID') and snmp_engine.snmpEngineID.prettyPrint() or '<unknown>',)) if not v3_context_engine_ids: v3_context_engine_ids.append((None, [])) log.msg.inc_ident() log.info('--- Simulation data recordings configuration') for v3_context_engine_id, ctx_data_dirs in v3_context_engine_ids: snmp_context = context.SnmpContext(snmp_engine, v3_context_engine_id) # unregister default context snmp_context.unregisterContextName(null) log.info( 'SNMPv3 Context Engine ID: ' '%s' % snmp_context.contextEngineId.prettyPrint()) data_index_instrum_controller = controller.DataIndexInstrumController() with daemon.PrivilegesOf(args.process_user, args.process_group): configure_managed_objects( ctx_data_dirs or data_dirs or confdir.data, data_index_instrum_controller, snmp_engine, snmp_context ) # Configure access to data index config.addV1System(snmp_engine, 'index', 'index', contextName='index') log.info('--- SNMPv3 USM configuration') if not v3_users: v3_users = ['simulator'] v3_auth_keys[v3_users[0]] = 'auctoritas' v3_auth_protos[v3_users[0]] = 'MD5' v3_priv_keys[v3_users[0]] = 'privatus' v3_priv_protos[v3_users[0]] = 'DES' for v3User in v3_users: if v3User in v3_auth_keys: if v3User not in v3_auth_protos: v3_auth_protos[v3User] = 'MD5' elif v3User in v3_auth_protos: log.error( 'auth protocol configured without key for user ' '%s' % v3User) return 1 else: v3_auth_keys[v3User] = None v3_auth_protos[v3User] = 'NONE' if v3User in v3_priv_keys: if v3User not in v3_priv_protos: v3_priv_protos[v3User] = 'DES' elif v3User in v3_priv_protos: log.error( 'privacy protocol configured without key for user ' '%s' % v3User) return 1 else: v3_priv_keys[v3User] = None v3_priv_protos[v3User] = 'NONE' if (AUTH_PROTOCOLS[v3_auth_protos[v3User]] == config.usmNoAuthProtocol and PRIV_PROTOCOLS[v3_priv_protos[v3User]] != config.usmNoPrivProtocol): log.error( 'privacy impossible without authentication for USM user ' '%s' % v3User) return 1 try: config.addV3User( snmp_engine, v3User, AUTH_PROTOCOLS[v3_auth_protos[v3User]], v3_auth_keys[v3User], PRIV_PROTOCOLS[v3_priv_protos[v3User]], v3_priv_keys[v3User]) except error.PySnmpError as exc: log.error( 'bad USM values for user %s: ' '%s' % (v3User, exc)) return 1 log.info('SNMPv3 USM SecurityName: %s' % v3User) if AUTH_PROTOCOLS[v3_auth_protos[v3User]] != config.usmNoAuthProtocol: log.info( 'SNMPv3 USM authentication key: %s, ' 'authentication protocol: ' '%s' % (v3_auth_keys[v3User], v3_auth_protos[v3User])) if PRIV_PROTOCOLS[v3_priv_protos[v3User]] != config.usmNoPrivProtocol: log.info( 'SNMPv3 USM encryption (privacy) key: %s, ' 'encryption protocol: ' '%s' % (v3_priv_keys[v3User], v3_priv_protos[v3User])) snmp_context.registerContextName('index', data_index_instrum_controller) log.info( 'Maximum number of variable bindings in SNMP response: ' '%s' % local_max_var_binds) log.info('--- Transport configuration') if not agent_udpv4_endpoints and not agent_udpv6_endpoints: log.error( 'agent endpoint address(es) not specified for SNMP ' 'engine ID %s' % v3_engine_id) return 1 for agent_udpv4_endpoint in agent_udpv4_endpoints: transport_domain = udp.domainName + (transport_index['udpv4'],) transport_index['udpv4'] += 1 snmp_engine.registerTransportDispatcher( transport_dispatcher, transport_domain) config.addSocketTransport( snmp_engine, transport_domain, agent_udpv4_endpoint[0]) log.info( 'Listening at UDP/IPv4 endpoint %s, transport ID ' '%s' % (agent_udpv4_endpoint[1], '.'.join([str(handler) for handler in transport_domain]))) for agent_udpv6_endpoint in agent_udpv6_endpoints: transport_domain = udp6.domainName + (transport_index['udpv6'],) transport_index['udpv6'] += 1 snmp_engine.registerTransportDispatcher( transport_dispatcher, transport_domain) config.addSocketTransport( snmp_engine, transport_domain, agent_udpv6_endpoint[0]) log.info( 'Listening at UDP/IPv6 endpoint %s, transport ID ' '%s' % (agent_udpv6_endpoint[1], '.'.join([str(handler) for handler in transport_domain]))) # SNMP applications GetCommandResponder(snmp_engine, snmp_context) SetCommandResponder(snmp_engine, snmp_context) NextCommandResponder(snmp_engine, snmp_context) BulkCommandResponder( snmp_engine, snmp_context).maxVarBinds = local_max_var_binds log.msg.dec_ident() if opt[0] == 'end-of-options': # Load up the rest of MIBs while running privileged (snmp_engine .msgAndPduDsp .mibInstrumController .mibBuilder.loadModules()) break # Prepare for next engine ID configuration v3_context_engine_ids = [] data_dirs = [] local_max_var_binds = args.max_var_binds v3_users = [] v3_auth_keys = {} v3_auth_protos = {} v3_priv_keys = {} v3_priv_protos = {} agent_udpv4_endpoints = [] agent_udpv6_endpoints = [] try: v3_engine_id = opt[1] if not v3_engine_id or v3_engine_id.lower() == 'auto': snmp_engine = engine.SnmpEngine() else: snmp_engine = engine.SnmpEngine( snmpEngineID=univ.OctetString(hexValue=v3_engine_id)) except Exception as exc: log.error( 'SNMPv3 Engine initialization failed, EngineID "%s": ' '%s' % (v3_engine_id, exc)) return 1 config.addContext(snmp_engine, '') elif opt[0] == '--v3-context-engine-id': v3_context_engine_ids.append((univ.OctetString(hexValue=opt[1]), [])) elif opt[0] == '--data-dir': if v3_context_engine_ids: v3_context_engine_ids[-1][1].append(opt[1]) else: data_dirs.append(opt[1]) elif opt[0] == '--max-varbinds': local_max_var_binds = opt[1] elif opt[0] == '--v3-user': v3_users.append(opt[1]) elif opt[0] == '--v3-auth-key': if not v3_users: log.error('--v3-user should precede %s' % opt[0]) return 1 if v3_users[-1] in v3_auth_keys: log.error( 'repetitive %s option for user %s' % (opt[0], v3_users[-1])) return 1 v3_auth_keys[v3_users[-1]] = opt[1] elif opt[0] == '--v3-auth-proto': if opt[1].upper() not in AUTH_PROTOCOLS: log.error('bad v3 auth protocol %s' % opt[1]) return 1 else: if not v3_users: log.error('--v3-user should precede %s' % opt[0]) return 1 if v3_users[-1] in v3_auth_protos: log.error( 'repetitive %s option for user %s' % (opt[0], v3_users[-1])) return 1 v3_auth_protos[v3_users[-1]] = opt[1].upper() elif opt[0] == '--v3-priv-key': if not v3_users: log.error('--v3-user should precede %s' % opt[0]) return 1 if v3_users[-1] in v3_priv_keys: log.error( 'repetitive %s option for user %s' % (opt[0], v3_users[-1])) return 1 v3_priv_keys[v3_users[-1]] = opt[1] elif opt[0] == '--v3-priv-proto': if opt[1].upper() not in PRIV_PROTOCOLS: log.error('bad v3 privacy protocol %s' % opt[1]) return 1 else: if not v3_users: log.error('--v3-user should precede %s' % opt[0]) return 1 if v3_users[-1] in v3_priv_protos: log.error( 'repetitive %s option for user %s' % (opt[0], v3_users[-1])) return 1 v3_priv_protos[v3_users[-1]] = opt[1].upper() elif opt[0] == '--agent-udpv4-endpoint': agent_udpv4_endpoints.append(opt[1]) elif opt[0] == '--agent-udpv6-endpoint': agent_udpv6_endpoints.append(opt[1]) transport_dispatcher.jobStarted(1) # server job would never finish with daemon.PrivilegesOf(args.process_user, args.process_group, final=True): try: transport_dispatcher.runDispatcher() except KeyboardInterrupt: log.info('Shutting down process...') finally: if variation_modules: log.info('Shutting down variation modules:') for name, contexts in variation_modules.items(): body = contexts[0] try: body['shutdown'](options=body['args'], mode='variation') except Exception as exc: log.error( 'Variation module "%s" shutdown FAILED: ' '%s' % (name, exc)) else: log.info('Variation module "%s" shutdown OK' % name) transport_dispatcher.closeDispatcher() log.info('Process terminated') return 0