Beispiel #1
0
    def init(self, *priv):
        if len(priv) < 1:
            raise SnmpfwdError(
                'Bad syslog params, need at least facility, also accept priority, host, port, socktype (tcp|udp)'
            )
        if len(priv) < 2:
            priv = [priv[0], 'debug']
        if len(priv) < 3:
            priv = [priv[0], priv[1], 'localhost', 514, 'udp']
        if not priv[2].startswith('/'):
            if len(priv) < 4:
                priv = [priv[0], priv[1], priv[2], 514, 'udp']
            if len(priv) < 5:
                priv = [priv[0], priv[1], priv[2], 514, 'udp']
            priv = [priv[0], priv[1], priv[2], int(priv[3]), priv[4]]

        try:
            handler = handlers.SysLogHandler(
                priv[2].startswith('/') and priv[2] or (priv[2], int(priv[3])),
                priv[0].lower(),
                len(priv) > 4 and priv[4] == 'tcp' and socket.SOCK_STREAM
                or socket.SOCK_DGRAM)

        except Exception:
            raise SnmpfwdError('Bad syslog option(s): %s' % sys.exc_info()[1])

        handler.setFormatter(
            logging.Formatter('%(asctime)s %(name)s: %(message)s'))

        self._logger.addHandler(handler)
Beispiel #2
0
    def init(self, *priv):
        if not priv:
            raise SnmpfwdError('Bad log file params, need filename')
        if sys.platform[:3] == 'win':
            # fix possibly corrupted absolute windows path
            if len(priv[0]) == 1 and priv[0].isalpha() and len(priv) > 1:
                priv = [priv[0] + ':' + priv[1]] + list(priv[2:])

        maxsize = 0
        maxage = None
        if len(priv) > 1 and priv[1]:
            try:
                if priv[1][-1] == 'k':
                    maxsize = int(priv[1][:-1]) * 1024
                elif priv[1][-1] == 'm':
                    maxsize = int(priv[1][:-1]) * 1024 * 1024
                elif priv[1][-1] == 'g':
                    maxsize = int(priv[1][:-1]) * 1024 * 1024 * 1024
                elif priv[1][-1] == 'S':
                    maxage = ('S', int(priv[1][:-1]))
                elif priv[1][-1] == 'M':
                    maxage = ('M', int(priv[1][:-1]))
                elif priv[1][-1] == 'H':
                    maxage = ('H', int(priv[1][:-1]))
                elif priv[1][-1] == 'D':
                    maxage = ('D', int(priv[1][:-1]))
                else:
                    raise ValueError('Unknown log rotation criterion: %s' % priv[1][-1])

            except ValueError:
                raise SnmpfwdError(
                    'Error in timed log rotation specification. Use <NNN>k,m,g '
                    'for size or <NNN>S,M,H,D for time limits'
                )

        try:
            if maxsize:
                handler = handlers.RotatingFileHandler(priv[0], backupCount=30, maxBytes=maxsize)
            elif maxage:
                handler = self.TimedRotatingFileHandler(priv[0], backupCount=30, when=maxage[0], interval=maxage[1])
            else:
                handler = handlers.WatchedFileHandler(priv[0])

        except Exception:
            raise SnmpfwdError(
                'Failure configure logging: %s' % sys.exc_info()[1]
            )

        handler.setFormatter(logging.Formatter('%(message)s'))

        self._logger.addHandler(handler)

        self('Log file %s, rotation rules: %s' % (priv[0], maxsize and '> %sKB' % (maxsize/1024) or maxage and '%s%s' % (maxage[1], maxage[0]) or '<none>'))
Beispiel #3
0
    def init(self, *priv):
        if not priv:
            raise SnmpfwdError('Bad log file params, need filename')
        if sys.platform[:3] == 'win':
            # fix possibly corrupted absolute windows path
            if len(priv[0]) == 1 and priv[0].isalpha() and len(priv) > 1:
                priv = [priv[0] + ':' + priv[1]] + list(priv[2:])

        maxsize = 0
        maxage = None
        if len(priv) > 1 and priv[1]:
            if priv[1][-1] in ('k', 'K'):
                maxsize = int(priv[1][:-1]) * 1024
            elif priv[1][-1] in ('m', 'M'):
                maxsize = int(priv[1][:-1]) * 1024 * 1024
            elif priv[1][-1] in ('g', 'G'):
                maxsize = int(priv[1][:-1]) * 1024 * 1024 * 1024
            elif priv[1][-1] in ('h', 'H'):
                maxage = ('H', int(priv[1][:-1]))
            elif priv[1][-1] in ('d', 'D'):
                maxage = ('D', int(priv[1][:-1]))
            else:
                raise SnmpfwdError(
                    'Unknown log rotation criteria %s, use <NNN>K,M,G for size or <NNN>H,D for time limits'
                    % priv[1])

        try:
            if maxsize:
                handler = handlers.RotatingFileHandler(priv[0],
                                                       backupCount=30,
                                                       maxBytes=maxsize)
            elif maxage:
                handler = handlers.TimedRotatingFileHandler(priv[0],
                                                            backupCount=30,
                                                            when=maxage[0],
                                                            interval=maxage[1])
            else:
                handler = handlers.WatchedFileHandler(priv[0])

        except AttributeError:
            raise SnmpfwdError('Bad log rotation criteria: %s' %
                               sys.exc_info()[1])

        handler.setFormatter(logging.Formatter('%(message)s'))

        self._logger.addHandler(handler)

        self('Log file %s, rotation rules: %s' %
             (priv[0], maxsize and '> %sKB' %
              (maxsize / 1024) or maxage and '%s%s' %
              (maxage[1], maxage[0]) or '<none>'))
Beispiel #4
0
    def getTrunkAddr(a, port=0):
        f = lambda h, p=port: (h, int(p))
        try:
            return f(*a.split(':'))

        except Exception:
            raise SnmpfwdError('improper IPv4 endpoint %s' % a)
Beispiel #5
0
def setLevel(level):
    global logLevel

    try:
        logLevel = levelsMap[level]

    except KeyError:
        raise SnmpfwdError('Unknown log level "%s", known levels are: %s' % (level, ', '.join(levelsMap)))
Beispiel #6
0
def setLogger(progId, *priv, **options):
    global msg

    try:
        if not isinstance(msg, AbstractLogger) or options.get('force'):
            msg = methodsMap[priv[0]](progId, *priv[1:])

    except KeyError:
        raise SnmpfwdError('Unknown logging method "%s", known methods are: %s' % (priv[0], ', '.join(methodsMap.keys())))
Beispiel #7
0
    def init(self, *priv):
        try:
            handler = logging.StreamHandler(self.stream)

        except AttributeError:
            raise SnmpfwdError('Stream logger failure: %s' % sys.exc_info()[1])

        handler.setFormatter(logging.Formatter('%(asctime)s %(message)s'))

        self._logger.addHandler(handler)
Beispiel #8
0
def prepareDataElements(octets, secret):
    try:
        msg, octets = decoder.decode(octets, asn1Spec=Message())

    except SubstrateUnderrunError:
        return None, None, None, octets

    if msg['version'] != PROTOCOL_VERSION:
        raise SnmpfwdError('Unsupported protocol versions %s/%s' %
                           (msg['version'], PROTOCOL_VERSION))

    r, _ = decoder.decode(
        secret and crypto.decrypt(secret, msg['payload'].asOctets())
        or msg['payload'].asOctets(),
        asn1Spec=pduMap.get(msg['content-id']))

    rsp = {}

    if msg['content-id'] == MSG_TYPE_REQUEST:
        rsp['callflow-id'] = r['callflow-id']

        for k in ('snmp-engine-id', 'snmp-transport-domain',
                  'snmp-peer-address', 'snmp-peer-port', 'snmp-bind-address',
                  'snmp-bind-port', 'snmp-security-model',
                  'snmp-security-level', 'snmp-security-name',
                  'snmp-context-engine-id', 'snmp-context-name',
                  'snmp-credentials-id', 'snmp-context-id', 'snmp-content-id',
                  'snmp-peer-id'):
            rsp[k] = r[k]

        pdu, _ = decoder.decode(r['snmp-pdu'], asn1Spec=rfc1905.PDUs())
        rsp['snmp-pdu'] = pdu.getComponent()

    elif msg['content-id'] == MSG_TYPE_RESPONSE:
        rsp['error-indication'] = r['error-indication']
        if not r['error-indication'] and r['snmp-pdu']:
            pdu, _ = decoder.decode(r['snmp-pdu'], asn1Spec=rfc1905.PDUs())
            rsp['snmp-pdu'] = pdu.getComponent()

    elif msg['content-id'] == MSG_TYPE_ANNOUNCEMENT:
        rsp['trunk-id'] = r['trunk-id']

    elif msg['content-id'] == MSG_TYPE_PING:
        rsp['serial'] = r['serial']

    elif msg['content-id'] == MSG_TYPE_PONG:
        rsp['serial'] = r['serial']

    return msg['msg-id'], msg['content-id'], rsp, octets
Beispiel #9
0
def parseTransportAddress(transportDomain,
                          transportAddress,
                          transportOptions,
                          defaultPort=0):
    if (('transparent-proxy' in transportOptions
         or 'virtual-interface' in transportOptions)
            and '$' in transportAddress):
        addrMacro = transportAddress

        if transportDomain[:len(udp.domainName)] == udp.domainName:
            h, p = '0.0.0.0', defaultPort
        else:
            h, p = '::0', defaultPort

    else:
        addrMacro = None

        if transportDomain[:len(udp.domainName)] == udp.domainName:
            if ':' in transportAddress:
                h, p = transportAddress.split(':', 1)
            else:
                h, p = transportAddress, defaultPort
        else:
            hp = re.split(r'^\[(.*?)\]:([0-9]+)', transportAddress, maxsplit=1)
            if len(hp) != 4:
                raise SnmpfwdError('bad address specification')

            h, p = hp[1:3]

        try:
            p = int(p)

        except (ValueError, IndexError):
            raise SnmpfwdError('bad port specification')

    return (h, p), addrMacro
Beispiel #10
0
    def init(self, *priv):
        if len(priv) < 1:
            raise SnmpfwdError('Bad syslog params, need at least facility, also accept '
                               'host, port, socktype (tcp|udp)')
        if len(priv) < 2:
            priv = [priv[0], 'debug']
        if len(priv) < 3:
            # search for syslog local socket

            for dev in self.SYSLOG_SOCKET_PATHS:
                if os.path.exists(dev):
                    priv = [priv[0], priv[1], dev]
                    break
            else:
                priv = [priv[0], priv[1], 'localhost', 514, 'udp']

        if not priv[2].startswith('/'):
            if len(priv) < 4:
                priv = [priv[0], priv[1], priv[2], 514, 'udp']
            if len(priv) < 5:
                priv = [priv[0], priv[1], priv[2], 514, 'udp']
            priv = [priv[0], priv[1], priv[2], int(priv[3]), priv[4]]

        try:
            handler = handlers.SysLogHandler(
                address=priv[2].startswith('/') and priv[2] or (priv[2], int(priv[3])),
                facility=priv[0].lower(),
                socktype=len(priv) > 4 and priv[4] == 'tcp' and socket.SOCK_STREAM or socket.SOCK_DGRAM
            )

        except Exception:
            raise SnmpfwdError('Bad syslog option(s): %s' % sys.exc_info()[1])

        handler.setFormatter(logging.Formatter('%(asctime)s %(name)s: %(message)s'))

        self._logger.addHandler(handler)
Beispiel #11
0
def parseTrunkEndpoint(address, defaultPort=0):

    for af, pattern in IP_TEMPLATES:

        hp = re.split(pattern, address, maxsplit=1)
        if len(hp) == 5:
            if hp[1]:
                h, p = hp[1:3]

            elif hp[3]:
                h, p = hp[3], defaultPort

            else:
                continue

            try:
                p = int(p)

            except (ValueError, IndexError):
                raise SnmpfwdError('bad port specification: %s' % (address,))

            return af, h, p

    raise SnmpfwdError('bad address specification: %s' % (address,))
Beispiel #12
0
def _preparePingPongData(reqType, msgId, serial, secret):
    msg = Message()
    msg['version'] = PROTOCOL_VERSION
    msg['msg-id'] = msgId
    msg['content-id'] = reqType

    if msg['content-id'] == MSG_TYPE_PING:
        r = Ping()
    elif msg['content-id'] == MSG_TYPE_PONG:
        r = Pong()
    else:
        raise SnmpfwdError('not a ping-pong message')

    r['serial'] = serial

    msg['payload'] = encoder.encode(r)
    if secret:
        msg['payload'] = crypto.encrypt(secret, encoder.encode(r))
    else:
        msg['payload'] = encoder.encode(r)
    return encoder.encode(msg)
Beispiel #13
0
def main():
    class LogString(LazyLogString):

        GROUPINGS = [
            ['callflow-id'],
            ['trunk-id'],
            [
                'server-snmp-engine-id', 'server-snmp-transport-domain',
                'server-snmp-peer-address', 'server-snmp-peer-port',
                'server-snmp-bind-address', 'server-snmp-bind-port',
                'server-snmp-security-model', 'server-snmp-security-level',
                'server-snmp-security-name', 'server-snmp-context-engine-id',
                'server-snmp-context-name', 'server-snmp-pdu',
                'server-snmp-entity-id'
            ],
            [
                'server-snmp-credentials-id', 'server-snmp-context-id',
                'server-snmp-content-id', 'server-snmp-peer-id',
                'server-classification-id'
            ],
            [
                'snmp-peer-id', 'snmp-bind-address', 'snmp-bind-port',
                'snmp-peer-address', 'snmp-peer-port',
                'snmp-context-engine-id', 'snmp-context-name', 'snmp-pdu'
            ],
        ]

        FORMATTERS = {
            'server-snmp-pdu': LazyLogString.prettyVarBinds,
            'snmp-pdu': LazyLogString.prettyVarBinds,
        }

    def snmpCbFun(snmpEngine, sendRequestHandle, errorIndication, rspPDU,
                  cbCtx):

        trunkId, msgId, trunkReq, pluginIdList, reqCtx = cbCtx

        trunkRsp = {
            'callflow-id': trunkReq['callflow-id'],
            'snmp-pdu': rspPDU,
        }

        logCtx = LogString(trunkRsp)

        if errorIndication:
            log.info('received SNMP error-indication "%s"' % errorIndication,
                     ctx=logCtx)
            trunkRsp['error-indication'] = errorIndication

        if rspPDU:
            reqPdu = trunkReq['server-snmp-pdu']

            for pluginId in pluginIdList:
                if reqPdu.tagSet in rfc3411.notificationClassPDUs:
                    st, rspPDU = pluginManager.processNotificationResponse(
                        pluginId, snmpEngine, rspPDU, trunkReq, reqCtx)

                elif reqPdu.tagSet not in rfc3411.unconfirmedClassPDUs:
                    st, rspPDU = pluginManager.processCommandResponse(
                        pluginId, snmpEngine, rspPDU, trunkReq, reqCtx)
                else:
                    log.error('ignoring unsupported PDU', ctx=logCtx)
                    break

                if st == status.BREAK:
                    log.debug('plugin %s inhibits other plugins' % pluginId,
                              ctx=logCtx)
                    break

                elif st == status.DROP:
                    log.debug(
                        'received SNMP %s, plugin %s muted response' %
                        (errorIndication and 'error' or 'response', pluginId),
                        ctx=logCtx)
                    trunkRsp['snmp-pdu'] = None
                    break

        try:
            trunkingManager.sendRsp(trunkId, msgId, trunkRsp)

        except SnmpfwdError:
            log.error('received SNMP %s message, trunk message not sent "%s"' %
                      (msgId, sys.exc_info()[1]),
                      ctx=logCtx)
            return

        log.debug('received SNMP %s message, forwarded as trunk message #%s' %
                  (errorIndication and 'error' or 'response', msgId),
                  ctx=logCtx)

    #
    # The following needs proper support in pysnmp. Meanwhile - monkey patching!
    #

    def makeTargetAddrOverride(targetAddr):
        endpoints = []

        def getTargetAddr(snmpEngine, snmpTargetAddrName):
            addrInfo = list(targetAddr(snmpEngine, snmpTargetAddrName))

            if endpoints:
                peerAddr, bindAddr = endpoints.pop(), endpoints.pop()

                try:
                    addrInfo[1] = addrInfo[1].__class__(
                        peerAddr).setLocalAddress(bindAddr)

                except Exception:
                    raise PySnmpError(
                        'failure replacing bind address %s -> %s for transport '
                        'domain %s' % (addrInfo[1], bindAddr, addrInfo[0]))

            return addrInfo

        def updateEndpoints(bindAddr, peerAddr):
            endpoints.extend((bindAddr, peerAddr))

        return getTargetAddr, updateEndpoints

    lcd.getTargetAddr, updateEndpoints = makeTargetAddrOverride(
        lcd.getTargetAddr)

    def trunkCbFun(trunkId, msgId, trunkReq):

        for key in tuple(trunkReq):
            if key != 'callflow-id':
                trunkReq['server-' + key] = trunkReq[key]
                del trunkReq[key]

        trunkReq['trunk-id'] = trunkId

        k = [
            str(x) for x in (trunkReq['server-snmp-engine-id'],
                             trunkReq['server-snmp-transport-domain'],
                             trunkReq['server-snmp-peer-address'] + ':' +
                             str(trunkReq['server-snmp-peer-port']),
                             trunkReq['server-snmp-bind-address'] + ':' +
                             str(trunkReq['server-snmp-bind-port']),
                             trunkReq['server-snmp-security-model'],
                             trunkReq['server-snmp-security-level'],
                             trunkReq['server-snmp-security-name'],
                             trunkReq['server-snmp-context-engine-id'],
                             trunkReq['server-snmp-context-name'])
        ]

        k.append(snmpPduTypesMap.get(trunkReq['server-snmp-pdu'].tagSet, '?'))
        k.append('|'.join([
            str(x[0])
            for x in v2c.apiPDU.getVarBinds(trunkReq['server-snmp-pdu'])
        ]))
        k = '#'.join(k)

        for x, y in origCredIdList:
            if y.match(k):
                origPeerId = trunkReq[
                    'server-snmp-entity-id'] = macro.expandMacro(x, trunkReq)
                break
        else:
            origPeerId = None

        k = [
            str(x) for x in (trunkReq['server-snmp-credentials-id'],
                             trunkReq['server-snmp-context-id'],
                             trunkReq['server-snmp-content-id'],
                             trunkReq['server-snmp-peer-id'])
        ]
        k = '#'.join(k)

        for x, y in srvClassIdList:
            if y.match(k):
                srvClassId = trunkReq[
                    'server-classification-id'] = macro.expandMacro(
                        x, trunkReq)
                break
        else:
            srvClassId = None

        logCtx = LogString(trunkReq)

        errorIndication = None

        peerIdList = routingMap.get(
            (origPeerId, srvClassId, macro.expandMacro(trunkId, trunkReq)))
        if not peerIdList:
            log.error('unroutable trunk message #%s' % msgId, ctx=logCtx)
            errorIndication = 'no route to SNMP peer configured'

        cbCtx = trunkId, msgId, trunkReq, (), {}

        if errorIndication:
            snmpCbFun(None, None, errorIndication, None, cbCtx)
            return

        pluginIdList = pluginIdMap.get(
            (origPeerId, srvClassId, macro.expandMacro(trunkId, trunkReq)))
        for peerId in peerIdList:
            peerId = macro.expandMacro(peerId, trunkReq)

            trunkReqCopy = trunkReq.copy()

            (snmpEngine, contextEngineId, contextName, bindAddr, bindAddrMacro,
             peerAddr, peerAddrMacro) = peerIdMap[peerId]

            if bindAddrMacro:
                bindAddr = macro.expandMacro(bindAddrMacro, trunkReqCopy), 0

            if peerAddrMacro:
                peerAddr = macro.expandMacro(peerAddrMacro, trunkReqCopy), 161

            if bindAddr and peerAddr:
                updateEndpoints(bindAddr, peerAddr)

            trunkReqCopy['snmp-peer-id'] = peerId

            trunkReqCopy['snmp-context-engine-id'] = contextEngineId
            trunkReqCopy['snmp-context-name'] = contextName

            trunkReqCopy['snmp-bind-address'], trunkReqCopy[
                'snmp-bind-port'] = bindAddr
            trunkReqCopy['snmp-peer-address'], trunkReqCopy[
                'snmp-peer-port'] = peerAddr

            logCtx.update(trunkReqCopy)

            pdu = trunkReqCopy['server-snmp-pdu']

            if pluginIdList:
                reqCtx = {}

                cbCtx = trunkId, msgId, trunkReqCopy, pluginIdList, reqCtx

                for pluginNum, pluginId in enumerate(pluginIdList):

                    if pdu.tagSet in rfc3411.notificationClassPDUs:
                        st, pdu = pluginManager.processNotificationRequest(
                            pluginId, snmpEngine, pdu, trunkReqCopy, reqCtx)

                    elif pdu.tagSet not in rfc3411.unconfirmedClassPDUs:
                        st, pdu = pluginManager.processCommandRequest(
                            pluginId, snmpEngine, pdu, trunkReqCopy, reqCtx)

                    else:
                        log.error('ignoring unsupported PDU', ctx=logCtx)
                        break

                    if st == status.BREAK:
                        log.debug('plugin %s inhibits other plugins' %
                                  pluginId,
                                  ctx=logCtx)
                        cbCtx = trunkId, msgId, trunkReqCopy, pluginIdList[:
                                                                           pluginNum], reqCtx
                        break

                    elif st == status.DROP:
                        log.debug(
                            'received trunk message #%s, plugin %s muted request'
                            % (msgId, pluginId),
                            ctx=logCtx)
                        snmpCbFun(snmpEngine, None, None, None, cbCtx)
                        return

                    elif st == status.RESPOND:
                        log.debug(
                            'received trunk message #%s, plugin %s forced immediate response'
                            % (msgId, pluginId),
                            ctx=logCtx)
                        snmpCbFun(snmpEngine, None, None, pdu, cbCtx)
                        return

            snmpMessageSent = False

            if pdu.tagSet in rfc3411.notificationClassPDUs:
                if pdu.tagSet in rfc3411.unconfirmedClassPDUs:
                    try:
                        notificationOriginator.sendPdu(
                            snmpEngine, peerId,
                            macro.expandMacro(contextEngineId, trunkReq),
                            macro.expandMacro(contextName, trunkReq), pdu)

                        snmpMessageSent = True

                    except PySnmpError:
                        errorIndication = 'failure sending SNMP notification'
                        log.error('trunk message #%s, SNMP error: %s' %
                                  (msgId, sys.exc_info()[1]),
                                  ctx=logCtx)

                    else:
                        errorIndication = None

                    # respond to trunk right away
                    snmpCbFun(snmpEngine, None, errorIndication, None, cbCtx)

                else:
                    try:
                        notificationOriginator.sendPdu(
                            snmpEngine, peerId,
                            macro.expandMacro(contextEngineId, trunkReq),
                            macro.expandMacro(contextName, trunkReq), pdu,
                            snmpCbFun, cbCtx)

                        snmpMessageSent = True

                    except PySnmpError:
                        log.error('trunk message #%s, SNMP error: %s' %
                                  (msgId, sys.exc_info()[1]),
                                  ctx=logCtx)

            elif pdu.tagSet not in rfc3411.unconfirmedClassPDUs:
                try:
                    commandGenerator.sendPdu(
                        snmpEngine, peerId,
                        macro.expandMacro(contextEngineId, trunkReq),
                        macro.expandMacro(contextName, trunkReq), pdu,
                        snmpCbFun, cbCtx)

                    snmpMessageSent = True

                except PySnmpError:
                    errorIndication = 'failure sending SNMP command'
                    log.error('trunk message #%s, SNMP error: %s' %
                              (msgId, sys.exc_info()[1]),
                              ctx=logCtx)

                    # respond to trunk right away
                    snmpCbFun(snmpEngine, None, errorIndication, None, cbCtx)

            else:
                log.error('ignoring unsupported PDU', ctx=logCtx)

            if snmpMessageSent:
                log.debug(
                    'received trunk message #%s, forwarded as SNMP message' %
                    msgId,
                    ctx=logCtx)

    #
    # Main script body 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 Proxy Forwarder: client part. Receives SNMP PDUs via one or many
  encrypted trunks established with the Forwarder's Agent part(s) running
  elsewhere and routes PDUs to built-in SNMP Managers for further
  transmission towards SNMP Agents.
  Can implement complex routing and protocol conversion logic through
  analyzing parts of SNMP messages and matching them against proxying rules.

Documentation:
  http://snmplabs.com/snmpfwd/

%s
""" % helpMessage)
            return
        if opt[0] == '-v' or opt[0] == '--version':
            import snmpfwd
            import pysnmp
            import pyasn1
            sys.stderr.write("""\
SNMP Proxy Forwarder version %s, written by Ilya Etingof <*****@*****.**>
Using foundation libraries: pysnmp %s, pyasn1 %s.
Python interpreter: %s
Software documentation and support at http://snmplabs.com/snmpfwd/
%s
""" % (snmpfwd.__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(','),
                                   **dict(loggerName=PROGRAM_NAME +
                                          '.pysnmp')))
        elif opt[0] == '--debug-asn1':
            pyasn1_debug.setLogger(
                pyasn1_debug.Debug(*opt[1].split(','),
                                   **dict(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, **dict(force=True))

            if loggingLevel:
                log.setLevel(loggingLevel)

        except SnmpfwdError:
            sys.stderr.write('%s\r\n%s\r\n' % (sys.exc_info()[1], helpMessage))
            return

    try:
        cfgTree = cparser.Config().load(cfgFile)

    except SnmpfwdError:
        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()

    #
    # SNMPv3 CommandGenerator & NotificationOriginator implementation
    #

    origCredIdList = []
    srvClassIdList = []
    peerIdMap = {}
    pluginIdMap = {}
    routingMap = {}
    engineIdMap = {}

    commandGenerator = cmdgen.CommandGenerator()

    notificationOriginator = ntforg.NotificationOriginator()

    transportDispatcher = AsynsockDispatcher()
    transportDispatcher.registerRoutingCbFun(lambda td, t, d: td)
    transportDispatcher.setSocketMap()  # use global asyncore socket map

    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,
                                 **dict(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 SnmpfwdError:
                log.error('plugin %s not loaded: %s' %
                          (pluginId, sys.exc_info()[1]))
                return

    for peerEntryPath in cfgTree.getPathsToAttr('snmp-peer-id'):
        peerId = cfgTree.getAttrValue('snmp-peer-id', *peerEntryPath)
        if peerId in peerIdMap:
            log.error('duplicate snmp-peer-id=%s at %s' %
                      (peerId, '.'.join(peerEntryPath)))
            return

        log.info('configuring SNMP peer %s (at %s)...' %
                 (peerId, '.'.join(peerEntryPath)))

        engineId = cfgTree.getAttrValue('snmp-engine-id', *peerEntryPath)
        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': {},
                'credIds': set()
            }

            engineIdMap[engineId] = snmpEngine, snmpContext, snmpEngineMap

            log.info('new engine-id %s' %
                     snmpEngine.snmpEngineID.prettyPrint())

        transportDomain = cfgTree.getAttrValue('snmp-transport-domain',
                                               *peerEntryPath)
        transportDomain = rfc1902.ObjectName(str(transportDomain))

        if (transportDomain[:len(udp.domainName)] != udp.domainName and udp6
                and transportDomain[:len(udp6.domainName)] != udp6.domainName):
            log.error('unknown transport domain %s' % (transportDomain, ))
            return

        transportOptions = cfgTree.getAttrValue(
            'snmp-transport-options', *peerEntryPath,
            **dict(default=[], vector=True))

        bindAddr = cfgTree.getAttrValue('snmp-bind-address', *peerEntryPath)

        try:
            bindAddr, bindAddrMacro = endpoint.parseTransportAddress(
                transportDomain, bindAddr, transportOptions)

        except SnmpfwdError:
            log.error('bad snmp-bind-address specification %s at %s' %
                      (bindAddr, '.'.join(peerEntryPath)))
            return

        if transportDomain in snmpEngineMap['transportDomain']:
            log.info('using transport endpoint with transport ID %s' %
                     (transportDomain, ))

        else:
            if transportDomain[:len(udp.domainName)] == udp.domainName:
                transport = udp.UdpTransport()
            else:
                transport = udp6.Udp6Transport()

            snmpEngine.registerTransportDispatcher(transportDispatcher,
                                                   transportDomain)

            t = transport.openClientMode(bindAddr)

            if 'transparent-proxy' in transportOptions:
                t.enablePktInfo()
                t.enableTransparent()
            elif 'virtual-interface' in transportOptions:
                t.enablePktInfo()

            config.addSocketTransport(snmpEngine, transportDomain, t)

            snmpEngineMap['transportDomain'][transportDomain] = bindAddr[
                0], bindAddr[1], transportDomain
            log.info(
                'new transport endpoint at bind address [%s]:%s, options %s, transport ID %s'
                %
                (bindAddr[0], bindAddr[1], transportOptions
                 and '/'.join(transportOptions) or '<none>', transportDomain))

        securityModel = cfgTree.getAttrValue('snmp-security-model',
                                             *peerEntryPath)
        securityModel = rfc1902.Integer(securityModel)
        securityLevel = cfgTree.getAttrValue('snmp-security-level',
                                             *peerEntryPath)
        securityLevel = rfc1902.Integer(securityLevel)
        securityName = cfgTree.getAttrValue('snmp-security-name',
                                            *peerEntryPath)

        contextEngineId = cfgTree.getAttrValue('snmp-context-engine-id',
                                               *peerEntryPath,
                                               **dict(default=None))
        contextName = cfgTree.getAttrValue('snmp-context-name', *peerEntryPath,
                                           **dict(default=''))

        if securityModel in (1, 2):
            if securityName in snmpEngineMap['securityName']:
                if snmpEngineMap['securityName'][
                        securityName] == securityModel:
                    log.info('using security-name %s' % securityName)
                else:
                    log.error(
                        'security-name %s already in use at security-model %s'
                        % (securityName, securityModel))
                    return
            else:
                communityName = cfgTree.getAttrValue('snmp-community-name',
                                                     *peerEntryPath)
                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

        elif securityModel == 3:
            if securityName in snmpEngineMap['securityName']:
                if snmpEngineMap['securityName'][
                        securityName] == securityModel:
                    log.info('using USM security-name: %s' % securityName)
                else:
                    raise SnmpfwdError(
                        'security-name %s already in use at security-model %s'
                        % (securityName, securityModel))
            else:
                usmUser = cfgTree.getAttrValue('snmp-usm-user', *peerEntryPath)
                securityEngineId = cfgTree.getAttrValue(
                    'snmp-security-engine-id', *peerEntryPath,
                    **dict(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', *peerEntryPath,
                        **dict(default=config.usmHMACMD5AuthProtocol))
                    try:
                        usmAuthProto = authProtocols[usmAuthProto.upper()]
                    except KeyError:
                        pass
                    usmAuthProto = rfc1902.ObjectName(usmAuthProto)
                    usmAuthKey = cfgTree.getAttrValue('snmp-usm-auth-key',
                                                      *peerEntryPath)
                    log.info(
                        'new USM authentication key: %s, authentication protocol: %s'
                        % (usmAuthKey, usmAuthProto))

                    if securityLevel == 3:
                        usmPrivProto = cfgTree.getAttrValue(
                            'snmp-usm-priv-protocol', *peerEntryPath,
                            **dict(default=config.usmDESPrivProtocol))
                        try:
                            usmPrivProto = privProtocols[usmPrivProto.upper()]
                        except KeyError:
                            pass
                        usmPrivProto = rfc1902.ObjectName(usmPrivProto)
                        usmPrivKey = cfgTree.getAttrValue(
                            'snmp-usm-priv-key', *peerEntryPath,
                            **dict(default=None))
                        log.info(
                            'new USM encryption key: %s, encryption protocol: %s'
                            % (usmPrivKey, usmPrivProto))

                        config.addV3User(
                            snmpEngine,
                            usmUser,
                            usmAuthProto,
                            usmAuthKey,
                            usmPrivProto,
                            usmPrivKey,
                        )

                    else:
                        config.addV3User(snmpEngine,
                                         usmUser,
                                         usmAuthProto,
                                         usmAuthKey,
                                         securityEngineId=securityEngineId)

                else:
                    config.addV3User(snmpEngine,
                                     usmUser,
                                     securityEngineId=securityEngineId)

                snmpEngineMap['securityName'][securityName] = securityModel

        else:
            log.error('unknown security-model: %s' % securityModel)
            sys.exit(1)

        credId = '/'.join(
            [str(x) for x in (securityName, securityLevel, securityModel)])
        if credId in snmpEngineMap['credIds']:
            log.info('using credentials ID %s...' % credId)
        else:
            config.addTargetParams(
                snmpEngine, credId, securityName, securityLevel,
                securityModel == 3 and 3 or securityModel - 1)
            log.info(
                'new credentials %s, security-name %s, security-level %s, security-model %s'
                % (credId, securityName, securityLevel, securityModel))
            snmpEngineMap['credIds'].add(credId)

        peerAddr = cfgTree.getAttrValue('snmp-peer-address', *peerEntryPath)

        try:
            peerAddr, peerAddrMacro = endpoint.parseTransportAddress(
                transportDomain, peerAddr, transportOptions, defaultPort=161)

        except SnmpfwdError:
            log.error('bad snmp-peer-address specification %s at %s' %
                      (peerAddr, '.'.join(peerEntryPath)))
            return

        timeout = cfgTree.getAttrValue('snmp-peer-timeout', *peerEntryPath)
        retries = cfgTree.getAttrValue('snmp-peer-retries', *peerEntryPath)

        config.addTargetAddr(snmpEngine, peerId, transportDomain, peerAddr,
                             credId, timeout, retries)

        peerIdMap[
            peerId] = snmpEngine, contextEngineId, contextName, bindAddr, bindAddrMacro, peerAddr, peerAddrMacro

        log.info(
            'new peer ID %s, bind address %s, peer address %s, timeout %s*0.01 secs, retries %s, credentials ID %s'
            % (peerId, bindAddrMacro or '<default>', peerAddrMacro
               or '%s:%s' % peerAddr, timeout, retries, credId))

    duplicates = {}

    # TODO: rename orig-* into server-* and orig-snmp-peer-id into server-snmp-entity-id

    for origCredCfgPath in cfgTree.getPathsToAttr('orig-snmp-peer-id'):
        origCredId = cfgTree.getAttrValue('orig-snmp-peer-id',
                                          *origCredCfgPath)
        if origCredId in duplicates:
            log.error('duplicate orig-snmp-peer-id=%s at %s and %s' %
                      (origCredId, '.'.join(origCredCfgPath), '.'.join(
                          duplicates[origCredId])))
            return

        duplicates[origCredId] = origCredCfgPath

        k = '#'.join(
            (cfgTree.getAttrValue('orig-snmp-engine-id-pattern',
                                  *origCredCfgPath),
             cfgTree.getAttrValue('orig-snmp-transport-domain-pattern',
                                  *origCredCfgPath),
             cfgTree.getAttrValue('orig-snmp-peer-address-pattern',
                                  *origCredCfgPath),
             cfgTree.getAttrValue('orig-snmp-bind-address-pattern',
                                  *origCredCfgPath),
             cfgTree.getAttrValue('orig-snmp-security-model-pattern',
                                  *origCredCfgPath),
             cfgTree.getAttrValue('orig-snmp-security-level-pattern',
                                  *origCredCfgPath),
             cfgTree.getAttrValue('orig-snmp-security-name-pattern',
                                  *origCredCfgPath),
             cfgTree.getAttrValue('orig-snmp-context-engine-id-pattern',
                                  *origCredCfgPath),
             cfgTree.getAttrValue('orig-snmp-context-name-pattern',
                                  *origCredCfgPath),
             cfgTree.getAttrValue('orig-snmp-pdu-type-pattern',
                                  *origCredCfgPath),
             cfgTree.getAttrValue('orig-snmp-oid-prefix-pattern',
                                  *origCredCfgPath)))

        log.info(
            'configuring original SNMP peer ID %s (at %s), composite key: %s' %
            (origCredId, '.'.join(origCredCfgPath), k))

        origCredIdList.append((origCredId, re.compile(k)))

    duplicates = {}

    for srvClassCfgPath in cfgTree.getPathsToAttr('server-classification-id'):
        srvClassId = cfgTree.getAttrValue('server-classification-id',
                                          *srvClassCfgPath)
        if srvClassId in duplicates:
            log.error('duplicate server-classification-id=%s at %s and %s' %
                      (srvClassId, '.'.join(srvClassCfgPath), '.'.join(
                          duplicates[srvClassId])))
            return

        duplicates[srvClassId] = srvClassCfgPath

        k = '#'.join(
            (cfgTree.getAttrValue('server-snmp-credentials-id-pattern',
                                  *srvClassCfgPath),
             cfgTree.getAttrValue('server-snmp-context-id-pattern',
                                  *srvClassCfgPath),
             cfgTree.getAttrValue('server-snmp-content-id-pattern',
                                  *srvClassCfgPath),
             cfgTree.getAttrValue('server-snmp-peer-id-pattern',
                                  *srvClassCfgPath)))

        log.info(
            'configuring server classification ID %s (at %s), composite key: %s'
            % (srvClassId, '.'.join(srvClassCfgPath), k))

        srvClassIdList.append((srvClassId, re.compile(k)))

    del duplicates

    for pluginCfgPath in cfgTree.getPathsToAttr('using-plugin-id-list'):
        pluginIdList = cfgTree.getAttrValue('using-plugin-id-list',
                                            *pluginCfgPath,
                                            **dict(vector=True))
        log.info('configuring plugin ID(s) %s (at %s)...' %
                 (','.join(pluginIdList), '.'.join(pluginCfgPath)))
        for credId in cfgTree.getAttrValue('matching-orig-snmp-peer-id-list',
                                           *pluginCfgPath,
                                           **dict(vector=True)):
            for srvClassId in cfgTree.getAttrValue(
                    'matching-server-classification-id-list', *pluginCfgPath,
                    **dict(vector=True)):
                for trunkId in cfgTree.getAttrValue('matching-trunk-id-list',
                                                    *pluginCfgPath,
                                                    **dict(vector=True)):
                    k = credId, srvClassId, trunkId
                    if k in pluginIdMap:
                        log.error(
                            'duplicate snmp-credentials-id=%s and server-classification-id=%s and trunk-id=%s at plugin-id %s'
                            % (credId, srvClassId, trunkId,
                               ','.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-snmp-peer-id-list'):
        peerIdList = cfgTree.getAttrValue('using-snmp-peer-id-list',
                                          *routeCfgPath, **dict(vector=True))
        log.info('configuring routing entry with peer IDs %s (at %s)...' %
                 (','.join(peerIdList), '.'.join(routeCfgPath)))
        for credId in cfgTree.getAttrValue('matching-orig-snmp-peer-id-list',
                                           *routeCfgPath, **dict(vector=True)):
            for srvClassId in cfgTree.getAttrValue(
                    'matching-server-classification-id-list', *routeCfgPath,
                    **dict(vector=True)):
                for trunkId in cfgTree.getAttrValue('matching-trunk-id-list',
                                                    *routeCfgPath,
                                                    **dict(vector=True)):
                    k = credId, srvClassId, trunkId
                    if k in routingMap:
                        log.error(
                            'duplicate snmp-credentials-id=%s and server-classification-id=%s and trunk-id=%s at snmp-peer-id %s'
                            % (credId, srvClassId, trunkId,
                               ','.join(peerIdList)))
                        return
                    else:
                        for peerId in peerIdList:
                            if peerId not in peerIdMap:
                                log.error('missing peer-id %s at %s' %
                                          (peerId, '.'.join(routeCfgPath)))
                                return

                        routingMap[k] = peerIdList

    trunkingManager = TrunkingManager(trunkCbFun)

    for trunkCfgPath in cfgTree.getPathsToAttr('trunk-id'):
        trunkId = cfgTree.getAttrValue('trunk-id', *trunkCfgPath)
        secret = cfgTree.getAttrValue('trunk-crypto-key', *trunkCfgPath,
                                      **dict(default=''))
        secret = secret and (secret * ((16 // len(secret)) + 1))[:16]
        log.info('configuring trunk ID %s (at %s)...' %
                 (trunkId, '.'.join(trunkCfgPath)))
        connectionMode = cfgTree.getAttrValue('trunk-connection-mode',
                                              *trunkCfgPath)
        if connectionMode == 'client':
            trunkingManager.addClient(
                trunkId,
                parseTrunkEndpoint(
                    cfgTree.getAttrValue('trunk-bind-address', *trunkCfgPath)),
                parseTrunkEndpoint(
                    cfgTree.getAttrValue('trunk-peer-address', *trunkCfgPath),
                    30201),
                cfgTree.getAttrValue('trunk-ping-period',
                                     *trunkCfgPath,
                                     default=0,
                                     expect=int), secret)
            log.info(
                'new trunking client from %s to %s' %
                (cfgTree.getAttrValue('trunk-bind-address', *trunkCfgPath),
                 cfgTree.getAttrValue('trunk-peer-address', *trunkCfgPath)))
        if connectionMode == 'server':
            trunkingManager.addServer(
                parseTrunkEndpoint(
                    cfgTree.getAttrValue('trunk-bind-address', *trunkCfgPath),
                    30201),
                cfgTree.getAttrValue('trunk-ping-period',
                                     *trunkCfgPath,
                                     default=0,
                                     expect=int), secret)
            log.info(
                'new trunking server at %s' %
                (cfgTree.getAttrValue('trunk-bind-address', *trunkCfgPath)))

    transportDispatcher.registerTimerCbFun(trunkingManager.setupTrunks,
                                           random.randrange(1, 5))
    transportDispatcher.registerTimerCbFun(trunkingManager.monitorTrunks,
                                           random.randrange(1, 5))

    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

    # Python 2.4 does not support the "finally" clause

    with daemon.PrivilegesOf(procUser, procGroup, final=True):

        while True:
            try:
                transportDispatcher.runDispatcher()

            except (PySnmpError, SnmpfwdError, socket.error):
                log.error(str(sys.exc_info()[1]))
                continue

            except Exception:
                transportDispatcher.closeDispatcher()
                raise
Beispiel #14
0
        try:
            configFile = optionValue

            for lineNo, line in enumerate(open(configFile).readlines()):
                line = line.strip()

                if not line or line.startswith('#'):
                    continue

                try:
                    oidPatt, valPatt, valRepl, replCount = shlex.split(line)

                except ValueError:
                    raise SnmpfwdError('%s: syntax error at %s:%d: %s' %
                                       (PLUGIN_NAME, configFile, lineNo + 1,
                                        sys.exc_info()[1]))

                debug(
                    '%s: for OIDs like "%s" and values matching "%s" rewrite value into "%s" (max %s times)'
                    % (PLUGIN_NAME, oidPatt, valPatt, valRepl, replCount))

                rewriteList.append(
                    (re.compile(oidPatt), re.compile(valPatt), valRepl,
                     int(replCount)))

        except Exception:
            raise SnmpfwdError('%s: config file load failure: %s' %
                               (PLUGIN_NAME, sys.exc_info()[1]))

info('%s: plugin initialization complete' % PLUGIN_NAME)
Beispiel #15
0
def main():
    class CommandResponder(cmdrsp.CommandResponderBase):
        pduTypes = (rfc1905.SetRequestPDU.tagSet, rfc1905.GetRequestPDU.tagSet,
                    rfc1905.GetNextRequestPDU.tagSet,
                    rfc1905.GetBulkRequestPDU.tagSet)

        def handleMgmtOperation(self, snmpEngine, stateReference, contextName,
                                pdu, acInfo):
            trunkReq = gCurrentRequestContext.copy()

            trunkReq['snmp-pdu'] = pdu

            pluginIdList = trunkReq['plugins-list']

            logCtx = LogString(trunkReq)

            reqCtx = {}

            for pluginNum, pluginId in enumerate(pluginIdList):

                st, pdu = pluginManager.processCommandRequest(
                    pluginId, snmpEngine, pdu, trunkReq, 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)
                    self.releaseStateInformation(stateReference)
                    return

                elif st == status.RESPOND:
                    log.debug(
                        'received SNMP message, plugin %s forced immediate response'
                        % pluginId,
                        ctx=logCtx)

                    try:
                        self.sendPdu(snmpEngine, stateReference, pdu)

                    except PySnmpError:
                        log.error('failure sending SNMP response', ctx=logCtx)

                    else:
                        self.releaseStateInformation(stateReference)

                    return

            # pass query to trunk

            trunkIdList = trunkReq['trunk-id-list']
            if trunkIdList is None:
                log.error('no route configured', ctx=logCtx)
                self.releaseStateInformation(stateReference)
                return

            for trunkId in trunkIdList:

                cbCtx = pluginIdList, trunkId, trunkReq, snmpEngine, stateReference, reqCtx

                try:
                    msgId = trunkingManager.sendReq(trunkId, trunkReq,
                                                    self.trunkCbFun, cbCtx)

                except SnmpfwdError:
                    log.error(
                        'received SNMP message, message not sent to trunk "%s"'
                        % sys.exc_info()[1],
                        ctx=logCtx)
                    return

                log.debug(
                    'received SNMP message, forwarded as trunk message #%s' %
                    msgId,
                    ctx=logCtx)

        def trunkCbFun(self, msgId, trunkRsp, cbCtx):
            pluginIdList, trunkId, trunkReq, snmpEngine, stateReference, reqCtx = cbCtx

            for key in tuple(trunkRsp):
                if key != 'callflow-id':
                    trunkRsp['client-' + key] = trunkRsp[key]
                    del trunkRsp[key]

            trunkRsp['callflow-id'] = trunkReq['callflow-id']

            logCtx = LogString(trunkRsp)

            if trunkRsp['client-error-indication']:
                log.info(
                    'received trunk message #%s, remote end reported error-indication "%s", NOT responding'
                    % (msgId, trunkRsp['client-error-indication']),
                    ctx=logCtx)

            elif 'client-snmp-pdu' not in trunkRsp:
                log.info(
                    'received trunk message #%s, remote end does not send SNMP PDU, NOT responding'
                    % msgId,
                    ctx=logCtx)

            else:
                pdu = trunkRsp['client-snmp-pdu']

                for pluginId in pluginIdList:
                    st, pdu = pluginManager.processCommandResponse(
                        pluginId, snmpEngine, pdu, trunkReq, 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('failure sending SNMP response', ctx=logCtx)

                else:
                    log.debug(
                        'received trunk message #%s, forwarded as SNMP message'
                        % msgId,
                        ctx=logCtx)

            self.releaseStateInformation(stateReference)

    #
    # SNMPv3 NotificationReceiver implementation
    #

    class NotificationReceiver(ntfrcv.NotificationReceiver):
        pduTypes = (rfc1157.TrapPDU.tagSet, rfc1905.SNMPv2TrapPDU.tagSet)

        def processPdu(self, snmpEngine, messageProcessingModel, securityModel,
                       securityName, securityLevel, contextEngineId,
                       contextName, pduVersion, pdu, maxSizeResponseScopedPDU,
                       stateReference):

            trunkReq = gCurrentRequestContext.copy()

            if messageProcessingModel == 0:
                pdu = rfc2576.v1ToV2(pdu)

                # TODO: why this is not automatic?
                v2c.apiTrapPDU.setDefaults(pdu)

            trunkReq['snmp-pdu'] = pdu

            pluginIdList = trunkReq['plugins-list']

            logCtx = LogString(trunkReq)

            reqCtx = {}

            for pluginNum, pluginId in enumerate(pluginIdList):

                st, pdu = pluginManager.processNotificationRequest(
                    pluginId, snmpEngine, pdu, trunkReq, 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('plugin %s muted request' % pluginId, ctx=logCtx)
                    return

                elif st == status.RESPOND:
                    log.debug('plugin %s NOT forced immediate response' %
                              pluginId,
                              ctx=logCtx)
                    # TODO: implement immediate response for confirmed-class PDU
                    return

            # pass query to trunk

            trunkIdList = trunkReq['trunk-id-list']
            if trunkIdList is None:
                log.error('no route configured', ctx=logCtx)
                return

            for trunkId in trunkIdList:

                # TODO: pass messageProcessingModel to respond
                cbCtx = pluginIdList, trunkId, trunkReq, snmpEngine, stateReference, reqCtx

                try:
                    msgId = trunkingManager.sendReq(trunkId, trunkReq,
                                                    self.trunkCbFun, cbCtx)

                except SnmpfwdError:
                    log.error(
                        'received SNMP message, message not sent to trunk "%s" %s'
                        % (trunkId, sys.exc_info()[1]),
                        ctx=logCtx)
                    return

                log.debug(
                    'received SNMP message, forwarded as trunk message #%s' %
                    msgId,
                    ctx=logCtx)

        def trunkCbFun(self, msgId, trunkRsp, cbCtx):
            pluginIdList, trunkId, trunkReq, snmpEngine, stateReference, reqCtx = cbCtx

            for key in tuple(trunkRsp):
                if key != 'callflow-id':
                    trunkRsp['client-' + key] = trunkRsp[key]
                    del trunkRsp[key]

            trunkRsp['callflow-id'] = trunkReq['callflow-id']

            logCtx = LazyLogString(trunkReq, trunkRsp)

            if trunkRsp['client-error-indication']:
                log.info(
                    'received trunk message #%s, remote end reported error-indication "%s", NOT responding'
                    % (msgId, trunkRsp['client-error-indication']),
                    ctx=logCtx)
            else:
                if 'client-snmp-pdu' not in trunkRsp:
                    log.debug(
                        'received trunk message #%s -- unconfirmed SNMP message'
                        % msgId,
                        ctx=logCtx)
                    return

                pdu = trunkRsp['client-snmp-pdu']

                for pluginId in pluginIdList:
                    st, pdu = pluginManager.processNotificationResponse(
                        pluginId, snmpEngine, pdu, trunkReq, reqCtx)

                    if st == status.BREAK:
                        log.debug('plugin %s inhibits other plugins' %
                                  pluginId,
                                  ctx=logCtx)
                        break
                    elif st == status.DROP:
                        log.debug(
                            'received trunk message #%s, plugin %s muted response'
                            % (msgId, pluginId),
                            ctx=logCtx)
                        return

                log.debug(
                    'received trunk message #%s, forwarded as SNMP message' %
                    msgId,
                    ctx=logCtx)

                # TODO: implement response part

                # # Agent-side API complies with SMIv2
                # if messageProcessingModel == 0:
                #     PDU = rfc2576.v2ToV1(PDU, origPdu)
                #
                # statusInformation = {}
                #
                # # 3.4.3
                # try:
                #     snmpEngine.msgAndPduDsp.returnResponsePdu(
                #         snmpEngine, messageProcessingModel, securityModel,
                #         securityName, securityLevel, contextEngineId,
                #         contextName, pduVersion, rspPDU, maxSizeResponseScopedPDU,
                #         stateReference, statusInformation)
                #
                # except error.StatusInformation:
                #         log.error('processPdu: stateReference %s, statusInformation %s' % (stateReference, sys.exc_info()[1]))

    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'],
            ['trunk-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 requestObserver(snmpEngine, execpoint, variables, cbCtx):

        trunkReq = {
            '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'],
        }

        trunkReq['snmp-credentials-id'] = macro.expandMacro(
            credIdMap.get(
                (str(snmpEngine.snmpEngineID), variables['transportDomain'],
                 variables['securityModel'], variables['securityLevel'],
                 str(variables['securityName']))), trunkReq)

        k = '#'.join([
            str(x)
            for x in (variables['contextEngineId'], variables['contextName'])
        ])
        for x, y in contextIdList:
            if y.match(k):
                trunkReq['snmp-context-id'] = macro.expandMacro(x, trunkReq)
                break
            else:
                trunkReq['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):
                trunkReq['snmp-peer-id'] = macro.expandMacro(peerId, trunkReq)
                break
        else:
            trunkReq['snmp-peer-id'] = None

        pdu = variables['pdu']
        if pdu.tagSet == v1.TrapPDU.tagSet:
            pdu = rfc2576.v1ToV2(pdu)
            v2c.apiTrapPDU.setDefaults(pdu)

        k = '#'.join([
            snmpPduTypesMap.get(variables['pdu'].tagSet, '?'),
            '|'.join([str(x[0]) for x in v2c.apiTrapPDU.getVarBinds(pdu)])
        ])

        for x, y in contentIdList:
            if y.match(k):
                trunkReq['snmp-content-id'] = macro.expandMacro(x, trunkReq)
                break
            else:
                trunkReq['snmp-content-id'] = None

        trunkReq['plugins-list'] = pluginIdMap.get(
            (trunkReq['snmp-credentials-id'], trunkReq['snmp-context-id'],
             trunkReq['snmp-peer-id'], trunkReq['snmp-content-id']), [])
        trunkReq['trunk-id-list'] = trunkIdMap.get(
            (trunkReq['snmp-credentials-id'], trunkReq['snmp-context-id'],
             trunkReq['snmp-peer-id'], trunkReq['snmp-content-id']))

        cbCtx.clear()
        cbCtx.update(trunkReq)

    #
    # 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 pysnmp_debug.flagMap.keys() if x != 'mibview'
    ]), '|'.join([x for x in pyasn1_debug.flagMap.keys()]), '|'.join(
        log.methodsMap.keys()), '|'.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 Proxy Forwarder: server part. Receives SNMP requests at one or many
          built-in SNMP Agents and routes them to encrypted trunks established with
          Forwarder's Manager part(s) running elsewhere.
          Can implement complex routing logic through analyzing parts of SNMP messages
          and matching them against proxying rules.

        Documentation:
          http://snmpfwd.sourceforge.io/

    %s
    """ % helpMessage)
            return
        if opt[0] == '-v' or opt[0] == '--version':
            import snmpfwd
            import pysnmp
            import pyasn1
            sys.stderr.write("""\
        SNMP Proxy Forwarder version %s, written by Ilya Etingof <*****@*****.**>
        Using foundation libraries: pysnmp %s, pyasn1 %s.
        Python interpreter: %s
        Software documentation and support at https://github.com/etingof/snmpfwd
        %s
        """ % (snmpfwd.__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(','),
                                   **dict(loggerName=PROGRAM_NAME +
                                          '.pysnmp')))
        elif opt[0] == '--debug-asn1':
            pyasn1_debug.setLogger(
                pyasn1_debug.Debug(*opt[1].split(','),
                                   **dict(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]

    try:
        log.setLogger(PROGRAM_NAME, *loggingMethod, **dict(force=True))

        if loggingLevel:
            log.setLevel(loggingLevel)

    except SnmpfwdError:
        sys.stderr.write('%s\r\n%s\r\n' % (sys.exc_info()[1], helpMessage))
        return

    try:
        cfgTree = cparser.Config().load(cfgFile)
    except SnmpfwdError:
        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 = {}
    trunkIdMap = {}
    engineIdMap = {}

    transportDispatcher = AsynsockDispatcher()
    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,
                                 **dict(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>'))

        try:
            pluginManager.loadPlugin(pluginId, pluginMod, pluginOptions)

        except SnmpfwdError:
            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)

            CommandResponder(snmpEngine, snmpContext)

            NotificationReceiver(snmpEngine, None)

            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 in snmpEngineMap['transportDomain']:
            h, p, transportDomain = snmpEngineMap['transportDomain'][
                transportDomain]
            log.info('using transport endpoint %s:%s, transport ID %s' %
                     (h, p, transportDomain))
        else:
            if transportDomain[:len(udp.domainName)] == udp.domainName:
                transport = udp.UdpTransport()
            elif transportDomain[:len(udp6.domainName)] == udp6.domainName:
                transport = udp6.Udp6Transport()
            else:
                log.error('unknown transport domain %s' % (transportDomain, ))
                return

            h, p = cfgTree.getAttrValue('snmp-bind-address',
                                        *configEntryPath).split(':', 1)

            snmpEngine.registerTransportDispatcher(transportDispatcher,
                                                   transportDomain)

            transportOptions = cfgTree.getAttrValue(
                'snmp-transport-options', *configEntryPath,
                **dict(default=[], vector=True))

            t = transport.openServerMode((h, int(p)))

            if 'transparent-proxy' in transportOptions:
                t.enablePktInfo()
                t.enableTransparent()
            elif 'virtual-interface' in transportOptions:
                t.enablePktInfo()

            config.addSocketTransport(snmpEngine, transportDomain, t)

            snmpEngineMap['transportDomain'][
                transportDomain] = h, p, transportDomain

            log.info(
                'new transport endpoint %s:%s, options %s, transport ID %s' %
                (h, p, 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 SnmpfwdError(
                        '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)
                log.info(
                    'new USM user %s, security-model %s, security-level %s, security-name %s'
                    % (usmUser, securityModel, securityLevel, securityName))

                if securityLevel in (2, 3):
                    usmAuthProto = cfgTree.getAttrValue(
                        'snmp-usm-auth-protocol', *configEntryPath,
                        **dict(default=config.usmHMACMD5AuthProtocol))
                    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,
                            **dict(default=config.usmDESPrivProtocol))
                        usmPrivProto = rfc1902.ObjectName(usmPrivProto)
                        usmPrivKey = cfgTree.getAttrValue(
                            'snmp-usm-priv-key', *configEntryPath,
                            **dict(default=None))
                        log.info(
                            'new USM encryption key: %s, encryption protocol: %s'
                            % (usmPrivKey, usmPrivProto))

                        config.addV3User(snmpEngine, usmUser, usmAuthProto,
                                         usmAuthKey, usmPrivProto, usmPrivKey)

                    else:
                        config.addV3User(snmpEngine, usmUser, usmAuthProto,
                                         usmAuthKey)

                else:
                    config.addV3User(snmpEngine, usmUser)

                snmpEngineMap['securityName'][securityName] = securityModel

            configKey.append(securityModel)
            configKey.append(securityLevel)
            configKey.append(securityName)

        else:
            raise SnmpfwdError('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,
                **dict(vector=True)):
            for bindAddress in cfgTree.getAttrValue(
                    'snmp-bind-address-pattern-list', *peerCfgPath,
                    **dict(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, **dict(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,
                                            **dict(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,
                                           **dict(vector=True)):
            for peerId in cfgTree.getAttrValue('matching-snmp-peer-id-list',
                                               *pluginCfgPath,
                                               **dict(vector=True)):
                for contextId in cfgTree.getAttrValue(
                        'matching-snmp-context-id-list', *pluginCfgPath,
                        **dict(vector=True)):
                    for contentId in cfgTree.getAttrValue(
                            'matching-snmp-content-id-list', *pluginCfgPath,
                            **dict(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-trunk-id-list'):
        trunkIdList = cfgTree.getAttrValue('using-trunk-id-list',
                                           *routeCfgPath, **dict(vector=True))
        log.info('configuring destination trunk ID(s) %s (at %s)...' %
                 (','.join(trunkIdList), '.'.join(routeCfgPath)))
        for credId in cfgTree.getAttrValue('matching-snmp-credentials-id-list',
                                           *routeCfgPath, **dict(vector=True)):
            for peerId in cfgTree.getAttrValue('matching-snmp-peer-id-list',
                                               *routeCfgPath,
                                               **dict(vector=True)):
                for contextId in cfgTree.getAttrValue(
                        'matching-snmp-context-id-list', *routeCfgPath,
                        **dict(vector=True)):
                    for contentId in cfgTree.getAttrValue(
                            'matching-snmp-content-id-list', *routeCfgPath,
                            **dict(vector=True)):
                        k = credId, contextId, peerId, contentId
                        if k in trunkIdMap:
                            log.error(
                                'duplicate snmp-credentials-id %s, snmp-context-id %s, snmp-peer-id %s, snmp-content-id %s at trunk-id(s) %s'
                                % (credId, contextId, peerId, contentId,
                                   ','.join(trunkIdList)))
                            return
                        else:
                            trunkIdMap[k] = trunkIdList

                        log.info(
                            'configuring trunk routing to %s (at %s), composite key: %s'
                            % (','.join(trunkIdList), '.'.join(routeCfgPath),
                               '/'.join(k)))

    def dataCbFun(trunkId, msgId, msg):
        log.debug('message ID %s received from trunk %s' % (msgId, trunkId))

    trunkingManager = TrunkingManager(dataCbFun)

    def getTrunkAddr(a, port=0):
        f = lambda h, p=port: (h, int(p))
        try:
            return f(*a.split(':'))

        except Exception:
            raise SnmpfwdError('improper IPv4 endpoint %s' % a)

    for trunkCfgPath in cfgTree.getPathsToAttr('trunk-id'):
        trunkId = cfgTree.getAttrValue('trunk-id', *trunkCfgPath)
        secret = cfgTree.getAttrValue('trunk-crypto-key', *trunkCfgPath,
                                      **dict(default=''))
        secret = secret and (secret * ((16 // len(secret)) + 1))[:16]
        log.info('configuring trunk ID %s (at %s)...' %
                 (trunkId, '.'.join(trunkCfgPath)))
        connectionMode = cfgTree.getAttrValue('trunk-connection-mode',
                                              *trunkCfgPath)
        if connectionMode == 'client':
            trunkingManager.addClient(
                trunkId,
                getTrunkAddr(
                    cfgTree.getAttrValue('trunk-bind-address', *trunkCfgPath)),
                getTrunkAddr(
                    cfgTree.getAttrValue('trunk-peer-address', *trunkCfgPath),
                    30201),
                cfgTree.getAttrValue('trunk-ping-period',
                                     *trunkCfgPath,
                                     default=0,
                                     expect=int), secret)
            log.info(
                'new trunking client from %s to %s' %
                (cfgTree.getAttrValue('trunk-bind-address', *trunkCfgPath),
                 cfgTree.getAttrValue('trunk-peer-address', *trunkCfgPath)))
        if connectionMode == 'server':
            trunkingManager.addServer(
                getTrunkAddr(
                    cfgTree.getAttrValue('trunk-bind-address', *trunkCfgPath),
                    30201),
                cfgTree.getAttrValue('trunk-ping-period',
                                     *trunkCfgPath,
                                     default=0,
                                     expect=int), secret)
            log.info(
                'new trunking server at %s' %
                (cfgTree.getAttrValue('trunk-bind-address', *trunkCfgPath)))

    transportDispatcher.registerTimerCbFun(trunkingManager.setupTrunks,
                                           random.randrange(1, 5))
    transportDispatcher.registerTimerCbFun(trunkingManager.monitorTrunks,
                                           random.randrange(1, 5))

    try:
        daemon.dropPrivileges(procUser, procGroup)

    except Exception:
        log.error('can not drop privileges: %s' % sys.exc_info()[1])
        return

    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

    # Python 2.4 does not support the "finally" clause

    while True:
        try:
            transportDispatcher.runDispatcher()

        except (PySnmpError, SnmpfwdError, socket.error):
            log.error(str(sys.exc_info()[1]))
            continue

        except Exception:
            transportDispatcher.closeDispatcher()
            raise
Beispiel #16
0
    if optionName == 'config':
        try:
            configFile = optionValue

            for line in open(configFile).readlines():
                line = line.strip()

                if not line or line.startswith('#'):
                    continue

                try:
                    skip, begin, end = line.split()

                except ValueError:
                    raise SnmpfwdError('%s: bad configuration syntax: "%s"' %
                                       (PLUGIN_NAME, line))

                try:
                    skip = v2c.ObjectIdentifier(skip)
                    begin = v2c.ObjectIdentifier(begin)
                    end = v2c.ObjectIdentifier(end)

                except Exception:
                    raise SnmpfwdError('%s: malformed OID %s/%s/%s' %
                                       (PLUGIN_NAME, skip, begin, end))

                oidsList.append((skip, begin, end))

            oidsList.sort(key=lambda x: x[0])

            idx = 0
Beispiel #17
0
                        'snmp-usm-priv-protocol', *peerEntryPath,
                        **dict(default=config.usmDESPrivProtocol))
                    usmPrivProto = rfc1902.ObjectName(usmPrivProto)
                    usmPrivKey = cfgTree.getAttrValue('snmp-usm-priv-key',
                                                      *peerEntryPath,
                                                      **dict(default=None))
                    log.msg(
                        'new USM encryption key: %s, encryption protocol: %s' %
                        (usmPrivKey, usmPrivProto))
            snmpEngineMap['securityName'][securityName] = securityModel

        config.addV3User(snmpEngine, usmUser, usmAuthProto, usmAuthKey,
                         usmPrivProto, usmPrivKey)

    else:
        raise SnmpfwdError('unknown security-model: %s' % securityModel)

    credId = '/'.join(
        [str(x) for x in (securityName, securityLevel, securityModel)])
    if credId in snmpEngineMap['credIds']:
        log.msg('using credentials ID %s...' % credId)
    else:
        config.addTargetParams(snmpEngine, credId, securityName, securityLevel,
                               securityModel == 3 and 3 or securityModel - 1)
        log.msg(
            'new credentials %s, security-name %s, security-level %s, security-model %s'
            % (credId, securityName, securityLevel, securityModel))
        snmpEngineMap['credIds'].add(credId)

    peerAddrMacro = None
    peerAddr = cfgTree.getAttrValue('snmp-peer-address', *peerEntryPath)
Beispiel #18
0
    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.msg('using security-name %s' % securityName)
            else:
                raise SnmpfwdError(
                    '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.msg(
                '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)
Beispiel #19
0
    rotation = config.get('file', 'rotation')

    if rotation == 'timed':

        filename = config.get('file', 'destination')

        handler = log.FileLogger.TimedRotatingFileHandler(
            filename,
            config.get('file', 'timescale'),
            int(config.get('file', 'interval')),
            int(config.get('file', 'backupcount'))
        )

    else:
        raise SnmpfwdError('%s: unknown rotation method %s' % (PLUGIN_NAME, rotation))

elif method == 'syslog':

    transport = config.get('syslog', 'transport')

    if transport in SYSLOG_TRANSPORTS:
        address = (
            config.get('syslog', 'host'),
            int(config.get('syslog', 'port'))
        )

    else:
        address = None

        for dev in SYSLOG_SOCKET_PATHS:
Beispiel #20
0
        config = RawConfigParser()
        config.read(configFile)

        method = config.get('general', 'method')
        if method == 'file':

            rotation = config.get('file', 'rotation')
            if rotation == 'timed':
                handler = handlers.TimedRotatingFileHandler(
                    config.get('file', 'destination'),
                    config.get('file', 'timescale'),
                    int(config.get('file', 'interval')),
                    int(config.get('file', 'backupcount'))
                )
            else:
                raise SnmpfwdError('%s: unknown rotation method %s at %s' % (PLUGIN_NAME, rotation, configFile))
        else:
            raise SnmpfwdError('%s: unknown logging method %s at %s' % (PLUGIN_NAME, method, configFile))

        debug('%s: using %s logging method' % (PLUGIN_NAME, method))

        logger.setLevel(logging.INFO)

        pdus = config.get('content', 'pdus')
        for pdu in pdus.split():
            try:
                pduMap[getattr(v2c, pdu + 'PDU').tagSet] = pdu

            except AttributeError:
                raise SnmpfwdError('%s: unknown PDU %s' % (PLUGIN_NAME, pdu))