#!/usr/local/bin/python -O """ Implements a simple Telnet like server that interacts with user via command line. User instructs server to query arbitrary SNMP agent and report back to user when SNMP reply is received. Server may poll a number of SNMP agents in the same time as well as handle a number of user sessions simultaneously. Since MIB parser is not yet implemented in Python, this script takes and reports Object IDs in dotted numeric representation only. Written by Ilya Etingof , 2000-2002. """ import time import string import socket from types import ClassType import asyncore import asynchat import getopt # Import PySNMP modules from pysnmp import asn1, v1, v2c from pysnmp import asynrole, error # Initialize a dictionary of running telnet engines global_engines = {} class telnet_engine(asynchat.async_chat): """Telnet engine class. Implements command line user interface and SNMP request queue management. Each new SNMP request message context is put into a pool of pending requests. As response arrives, it's matched against each of pending message contexts. """ def __init__ (self, sock): # Call constructor of the parent class asynchat.async_chat.__init__ (self, sock) # Set up input line terminator self.set_terminator ('\n') # Initialize input data buffer self.data = '' # Create async SNMP transport manager self.manager = asynrole.manager(self.request_done_fun) # A queue of pending requests self.pending = [] # Run our own source of SNMP message serial number to preserve its # sequentiality self.request_id = 1 # Register this instance of telnet engine at a global dictionary # used for their periodic invocations for timeout purposes global_engines[self] = 1 # User interface goodies self.prompt = 'query> ' self.welcome = 'Example Telnet server / SNMP manager ready.\n' # Commands help messages commands = 'Commands:\n' for mod in (v1, v2c): rtypes = dir(mod) for rtype in rtypes: if rtype[-7:] == 'REQUEST': commands = commands + ' ' + '%-14s' % rtype[:-7] + \ ' issue SNMP (%s) %s request\n' % \ (mod.__name__[7:], rtype[:-7]) # Options help messages options = 'Options:\n' options = options + ' -p port to communicate with at the agent. Default is 161.\n' options = options + ' -t response timeout. Default is 1.' self.usage = 'Syntax: [options] ' + str(val) + '\n' # Send reply back to user self.push(reply + self.prompt) def tick(self, now): """This method gets invoked periodically from upper scope for generic housekeeping purposes. """ # Walk over pending message context for (req, dst, expire) in self.pending: # Expire long pending contexts if expire < now: # Send a notice on expired request context self.push('WARNING: expiring long pending request %s destined %s\n' % (req, dst)) # Expire context self.pending.remove((req, dst, expire)) class telnet_server(asyncore.dispatcher): """Telnet server class. Listens for incoming requests and creates instances of telnet engine classes for handling new session. """ def __init__(self, port=8989): """Takes optional TCP port number for the server to bind to. """ # Call parent class constructor explicitly asyncore.dispatcher.__init__(self) # Create socket of requested type self.create_socket(socket.AF_INET, socket.SOCK_STREAM) # Set it to re-use address self.set_reuse_addr() # Bind to all interfaces of this host at specified port self.bind(('', port)) # Start listening for incoming requests self.listen(5) def handle_accept(self): """Called by asyncore engine when new connection arrives """ # Accept new connection (sock, addr) = self.accept() # Create an instance of Telnet engine class to handle this new user # session and pass it socket object to use any further telnet_engine(sock) # Run the module if it's invoked for execution if __name__ == '__main__': # Create an instance of Telnet superserver server = telnet_server () # Start the select() I/O multiplexing loop while 1: # Deliver 'tick' event to every instance of telnet engine map(lambda x: x.tick(time.time()), global_engines.keys()) # Do the I/O on active sockets asyncore.poll(1.0)