1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
|
#!/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 <ilya@glas.net>, 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> port to communicate with at the agent. Default is 161.\n'
options = options + ' -t <timeout> response timeout. Default is 1.'
self.usage = 'Syntax: <command> [options] <snmp-agent> <community> <obj-id [[obj-id] ... ]\n'
self.usage = self.usage + commands + options
# Send welcome message down to user. By using push() method we
# having asynchronous engine delivering welcome message to user.
self.push(self.welcome + self.usage + '\n' + self.prompt)
def collect_incoming_data (self, data):
"""Put data read from socket to a buffer
"""
# Collect data in input buffer
self.data = self.data + data
def found_terminator (self):
"""This method is called by asynchronous engine when it finds
command terminator in the input stream
"""
# Take the complete line and reset input buffer
line = self.data
self.data = ''
# Handle user command
response = self.process_command(line)
# Reply back to user whenever there is anything to reply
if response:
self.push(response + '\n')
self.push(self.prompt)
def handle_error(self, exc_type, exc_value, exc_traceback):
"""Invoked by asyncore on any exception
"""
# In case of PySNMP exception, report it to user. Otherwise,
# just pass the exception on.
if type(exc_type) == ClassType and\
issubclass(exc_type, error.Generic):
self.push('Exception: %s: %s\n' % (exc_type, exc_value))
else:
raise (exc_type, exc_value)
def handle_close(self):
"""Invoked by asyncore on connection termination
"""
# Get this instance of telnet engine off the global pool
del global_engines[self]
# Pass connection close event to accompanying asyncore objects
self.manager.close()
asynchat.async_chat.close(self)
def process_command (self, line):
"""Process user input
"""
# Initialize defaults
port = 161
timeout = 1
version = '1'
# Split request up to fields
args = string.split(line)
# Make sure request is not empty
if not args:
return
# Parse command
cmd = string.upper(args[0])
# Parse possible options
try:
(opts, args) = getopt.getopt(args[1:], 'hp:t:v:',\
['help', 'port=', \
'version=', 'timeout='])
except getopt.error, why:
return 'getopt error: %s\n%s' % (why, self.usage)
try:
for opt in opts:
if opt[0] == '-h' or opt[0] == '--help':
return self.usage
if opt[0] == '-p' or opt[0] == '--port':
port = int(opt[1])
if opt[0] == '-t' or opt[0] == '--timeout':
timeout = int(opt[1])
if opt[0] == '-v' or opt[0] == '--version':
version = opt[1]
except ValueError, why:
return 'Bad parameter \'%s\' for option %s: %s\n%s' \
% (opt[1], opt[0], why, self.usage)
# Make sure we got enough arguments
if len(args) < 3:
return 'Insufficient number of arguments\n%s' % self.usage
# Create a SNMP request&response objects from protocol version
# specific module.
try:
req = eval('v' + version + '.' + cmd + 'REQUEST')()
except (NameError, AttributeError):
return 'Unsupported SNMP protocol version/request type: %s/%s\n%s'\
% (version, cmd, self.usage)
# Update request ID
self.request_id = self.request_id + 1
# Build complete SNMP request message and pass it over to
# transport layer to send it out
self.manager.send(req.encode(request_id=self.request_id,\
community=args[1], \
encoded_oids=map(asn1.OBJECTID().encode,\
args[2:])),\
(args[0], port))
# Add request details into a pool of pending requests
self.pending.append((req, (args[0], port), time.time() + timeout))
def request_done_fun(self, manager, data, (response, src),\
(exc_type, exc_value, exc_traceback)):
"""Callback method invoked by SNMP manager object as response
arrives. Asynchronous SNMP manager object passes back a
reference to object instance that initiated this SNMP request
alone with SNMP response message.
"""
if exc_type is not None:
apply(self.handle_error, (exc_type, exc_value, exc_traceback))
return
# Initialize response buffer
reply = 'Response from agent %s:\n' % str(src)
# Decode response message
(rsp, rest) = v2c.decode(response)
# Walk over pending message context
for (req, dst, expire) in self.pending:
if req == rsp:
# Take matched context off the pending pool
self.pending.remove((req, dst, expire))
break
else:
reply = reply +\
'WARNING: dropping unmatched (late) response: \n'\
+ str(rsp)
self.push(reply + self.prompt)
return
# Decode BER encoded Object IDs.
oids = map(lambda x: x[0], map(asn1.OBJECTID().decode, \
rsp['encoded_oids']))
# Decode BER encoded values associated with Object IDs.
vals = map(lambda x: x[0](), map(asn1.decode, rsp['encoded_vals']))
# Convert two lists into a list of tuples and print 'em all
for (oid, val) in map(None, oids, vals):
reply = reply + oid + ' ---> ' + 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)
|