#!/usr/bin/env python
"""
   Receive and handle SNMP trap events.

   Copyright 1999-2004 by Ilya Etingof <ilya@glas.net>. See LICENSE for
   details.
"""
import signal, os, sys, time, string

from pysnmp.version import verifyVersionRequirement
verifyVersionRequirement(3, 4)

from pysnmp.error import PySnmpError
from pysnmp.mapping.udp.role import Agent
from pysnmp.proto.api import alpha
from pysnmpap.cli import options, ucd
from pysnmpap.error import SnmpApplicationError

class ConfForeground(options.ConfOptionFlag):
    def help(self): return 'do not detach from control terminal [%s]' % self
confForeground = ConfForeground(0)

class ConfLogSyslog(options.ConfOptionValue):
    def help(self): return 'log incoming traps to this syslog(3) facility [%s]' % self
confLogSyslog = ConfLogSyslog('')

class ConfLogfile(options.ConfOptionValue):
    def help(self): return 'log incoming traps to this file [%s]' % self
confLogfile = ConfLogfile('')

class ConfLogStderr(options.ConfOptionFlag):
    def help(self): return 'log incoming traps to sys.stderr [%s]' % self
confLogStderr = ConfLogStderr(0)

class ConfPidfile(options.ConfOptionValue):
    def help(self): return 'save PID of trap daemon to this file [%s]' % self
confPidfile = ConfPidfile('')

class ConfPythonModule(options.ConfOptionValue):
    def help(self): return 'execfile() this .py file and pass trap params as \'wholeMsg\', \'transportDsp\', \'transportDomain\', \'transportAddress\' locals (API MIGHT CHANGE!) [%s]' % self
confPythonModule = ConfPythonModule('')

def formatUsage():
    tspDsp = Agent()
    appUsage = '[ -h ] [ -f ] [ -P ] [ -l <facility> ] [ -o <logfile> ] [ -u <pidfile> ] [ -p <pyfile> ]'
    appOpts =           ' -h             print this usage note.\n'
    appOpts = appOpts + ' -f             ' + confForeground.help() + '\n'
    appOpts = appOpts + ' -P             ' + confLogStderr.help() + '\n'
    appOpts = appOpts + ' -s <facility>  ' + confLogSyslog.help() + '\n'
    appOpts = appOpts + ' -o <logfile>   ' + confLogfile.help() + '\n'
    appOpts = appOpts + ' -u <pidfile>   ' + confPidfile.help() + '\n'
    appOpts = appOpts + ' -p <pymodule>  ' + confPythonModule.help() + '\n'

    usage = 'Usage: ' + sys.argv[0] + ' ' + appUsage + ' ' + \
            tspDsp.cliUcdGetUsage()
    options = 'Application options:\n' + appOpts + \
              'Transport options:\n' + tspDsp.cliUcdGetOptsUsage(' ')
    return usage + '\n' + options

# Parse app options
idx = 1; newArgv = []
while idx < len(sys.argv):
    if sys.argv[idx] == '-h':
        sys.stderr.write(formatUsage() + '\n')
        sys.exit(1)
    if sys.argv[idx] == '-f':
        confForeground(1)
        idx = idx + 1
        continue
    if sys.argv[idx] == '-P':
        confLogStderr(1); confLogSyslog('')
        idx = idx + 1
        continue
    try:
        if sys.argv[idx] == '-s':
            confLogSyslog(sys.argv[idx+1])
            idx = idx + 2
            continue
        if sys.argv[idx] == '-o':
            confLogfile(sys.argv[idx+1]); confLogSyslog('')
            idx = idx + 2
            continue
        if sys.argv[idx] == '-u':
            confPidfile(sys.argv[idx+1])
            idx = idx + 2
            continue
        if sys.argv[idx] == '-p':
            confPythonModule(sys.argv[idx+1])
            idx = idx + 2
            continue
    except IndexError, why:
        sys.stderr.write('Missing value to %s\n%s\n' % \
                     (sys.argv[idx], formatUsage()))
        sys.exit(-1)
    newArgv.append(sys.argv[idx])
    idx = idx + 1

# Proto & transport objects
tspDsp = Agent()
metaReq = alpha.MetaMessage()

# Parse transport options
try:
    newArgv = tspDsp.cliUcdSetArgs(newArgv)
except SnmpApplicationError, why:
    sys.stderr.write('Error: %s\n%s\n' % (why, formatUsage()))
    sys.exit(-1)

if len(newArgv):
    sys.stderr.write('Extra parameters given: %s\n%s\n' \
                     % (newArgv, formatUsage()))
    sys.exit(-1)

if not confForeground:
    # Daemonize
    signal.signal(signal.SIGHUP, signal.SIG_IGN)
    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
    os.umask(022)
    if os.fork(): sys.exit(0)

    # Child continues
    os.setsid()
    os.chdir('/')

    f = open('/dev/null', 'rw+')
    os.dup2(f.fileno(), sys.stdin.fileno())
    os.dup2(f.fileno(), sys.stdout.fileno())
    os.dup2(f.fileno(), sys.stderr.fileno())
    
    if f.fileno() > 2: f.close()

if confLogSyslog:
    import syslog
    fname = 'LOG_'+string.upper(confLogSyslog())
    if not hasattr(syslog, fname):
        sys.stderr.write('Bad syslog facility name %s\n' % confLogSyslog)
        sys.exit(-1)
    syslog.openlog('pysnmptrapd', syslog.LOG_NDELAY|syslog.LOG_PID, \
                   getattr(syslog, fname))

if confPidfile:
    # Create PID file
    t = '%s.%d' % (confPidfile, os.getpid())
    try:
        f = open(t, 'w'); f.write('%d' % os.getpid()); f.close()
        os.rename(t, '%s' % confPidfile)
    except StandardError, why:
        sys.stderr.write('Cannot create pid file %s: %s\n' % \
                     (confPidfile, why))
        sys.exit(-1)

def logit(msg):
    if confLogSyslog: syslog.syslog(syslog.LOG_INFO, msg)
    elif confLogfile:
        f = open('%s' % confLogfile, 'a'); f.write(msg+'\n'); f.close()
    elif confLogStderr: sys.stderr.write(msg+'\n')

def cbFun(tspDsp, metaReq, (wholeMsg, srcAddr)):
    metaReq.decode(wholeMsg)
    req = metaReq.apiAlphaGetCurrentComponent()

    if req.apiAlphaGetPdu().apiAlphaGetPduType() == alpha.trapPduType:
        logit('*** %s %s (version ID %s) from %s: %s' % \
              (time.asctime(), \
               req.apiAlphaGetPdu().apiAlphaGetPduType(), \
               req.apiAlphaGetProtoVersionId(), \
               srcAddr,
               req.apiAlphaGetPdu()))

        if confPythonModule:
            lcls = { 'wholeMsg': req,
                     'transportDsp': tspDsp,
                     'transportDomain': 'udp',
                     'transportAddress': srcAddr }
            try:
                codeObj = execfile('%s' % confPythonModule, lcls, globals())
            except StandardError, why:
                logit('execfile() fails %s: %s' % (confPythonModule, why))
                sys.exit(-1)
                
    return (None, None)

logit('started')

while 1:
    try:
        tspDsp.receiveAndSend((cbFun, alpha.MetaMessage()))
    except PySnmpError, why:
        logit('Error: %s' % why)
    except Exception, why:
        logit('Fatal: %s' % why)
        break
    
if confPidfile: os.unlink('%s' % confPidfile)
