1
2
3
4
5
6
7
8
9
10
11
12
13
14 __doc__="""TelnetClient
15
16 TelnetClient is used by TelnetClient to issue commands to a machine
17 and return their output over the telnet protocol.
18
19 Device Tree Parameters are:
20
21 zTelnetLoginTries - number of times to try login default: 1
22 zTelnetLoginTimeout - timeout for expect statements during login default: 2
23 zTelnetPromptTimeout - pause used during prompt discovery default: 0.2
24 zTelnetCommandTimeout - default timeout when executing a command default: 5
25 zTelnetLoginRegex - regex to match the login prompt default: 'ogin:.$'
26 zTelnetPasswordRegex - regext to match the password prompt default: 'assword:.$'
27 zTelnetEnable - should enable mode should be entered: default False
28 zTelnetEnableRegex - regext to match the enable prompt default: 'assword:.$'
29
30 Other Parameters that are used by both TelnetClient and SshClient:
31 zCommandPathList - list of path to check for a command
32 zCommandExistanceCheck - shell command issued to look for executible
33 must echo succ if executible is found
34 default: test -f executible
35
36 """
37
38 import Globals
39
40
41 try:
42 from twisted.conch import telnet
43 except:
44 from twisted.protocols import telnet
45
46 from twisted.internet import reactor
47
48 import re
49 import logging
50 log = logging.getLogger("zen.TelnetClient")
51
52 import CollectorClient
53 from Exceptions import *
54
55 from Products.ZenUtils.Utils import unused
56
57 defaultPromptTimeout = 10
58 defaultLoginRegex = 'ogin:.$'
59 defaultPasswordRegex = 'assword:'
60 defaultEnable = False
61 defaultEnableRegex = 'assword:'
62 defaultEnablePassword = ''
63 defaultTermLength = False
64
65 responseMap = ("WILL", "WONT", "DO", "DONT")
66
68 """
69 Check to see if a device supports telnet
70
71 @param hostname: name or IP address of device
72 @type hostname: string
73 @return: whether or not telnet port is available
74 @rtype: integer
75 @todo: support alternate ports
76 """
77 from telnetlib import Telnet
78 import socket
79 try:
80 tn = Telnet(hostname)
81 tn.close()
82 return 1
83 except socket.error:
84 return 0
85
86
88 """
89 State-machine-based class for telnet
90
91 To switch from one state to the next, methods
92 return the next state.
93 """
94 mode = 'Login'
95
96 timeout = 0
97 timeoutID = None
98 p1 = ""
99 p2 = ""
100 commandPrompt = ""
101 command = ''
102 enabled = -1
103 scCallLater = None
104 bytes = ''
105 lastwrite = ''
106 result = ''
107 buffer = ""
108
122
123
125 """
126 Do we support this telnet feature?
127 Reply back appropriately.
128
129 @param feature: IAC feature request
130 @type feature: string
131 """
132 log.debug("Received telnet DO feature %s" % ord(feature))
133 if ord(feature) == 1:
134 self._iac_response(telnet.WILL, feature)
135 else:
136 self._iac_response(telnet.WONT, feature)
137
139 """
140 Do we support this telnet feature?
141 Reply back appropriately.
142
143 @param feature: IAC feature request
144 @type feature: string
145 """
146
147 log.debug("Received telnet DONT feature %s" % ord(feature))
148 self._iac_response(telnet.WONT, feature)
149
150
152 """
153 Do we support this telnet feature?
154 Reply back appropriately.
155
156 @param feature: IAC feature request
157 @type feature: string
158 """
159 log.debug("Received telnet WILL feature %s" % ord(feature))
160
161 self._iac_response(telnet.DONT, feature)
162
163
165 """
166 Do we support this telnet feature?
167 Reply back appropriately.
168
169 @param feature: IAC feature request
170 @type feature: string
171 """
172 log.debug("Received telnet WONT feature %s" % ord(feature))
173
174 self._iac_response(telnet.DONT, feature)
175
176
178 """
179 Respond to IAC request with our response
180
181 @param action: IAC action
182 @type action: string
183 @param feature: IAC feature request
184 @type feature: string
185 """
186 log.debug("Sending telnet action %s feature %s" %
187 (responseMap[ord(action)-251], ord(feature)))
188 self.write(telnet.IAC+action+feature)
189
190
192 """
193 Write data across the wire and record it.
194
195 @param data: data to write
196 @type data: string
197 """
198 self.lastwrite = data
199 self.transport.write(data)
200
201
203 """
204 Given data returned from the remote device, test out
205 the current chunk of data to determine whether
206 or not to switch states, or just add the chunk to
207 the list of data received from the host.
208
209 If we find the end condition for the state, process
210 the line.
211
212 @param chunk: data
213 @type chunk: string
214 """
215 self.buffer = self.buffer + chunk
216 regex = None
217 if self.factory.modeRegex.has_key(self.mode):
218 regex = self.factory.modeRegex[self.mode]
219 log.debug("Mode '%s' regex = %s" % (self.mode, regex))
220 log.debug("Chunk received = '%s'" % chunk)
221 if regex and re.search(regex, chunk):
222 self.processLine(self.buffer)
223 self.buffer = ""
224
225
227 """
228 Call a method that looks like 'telnet_*' where '*' is filled
229 in by the current mode. telnet_* methods should return a string which
230 will become the new mode.
231
232 @param line: data
233 @type line: string
234 """
235 line = re.sub("\r\n|\r", "\n", line)
236
237 if self.lastwrite.startswith(line):
238 self.lastwrite = self.lastwrite[len(line):]
239 line = ''
240 elif line.find(self.lastwrite) == 0:
241 line = line[len(self.lastwrite):]
242 log.debug("mode = %s", self.mode)
243 self.mode = getattr(self, "telnet_"+self.mode)(line)
244
245
247 """
248 Look for data and send to processLine()
249
250 @param data: output from telnet
251 @type data: string
252 """
253 telnet.Telnet.dataReceived(self, data)
254 log.debug('Line %r', self.bytes)
255 if self.bytes:
256 self.processLine(self.bytes)
257 self.bytes = ''
258
259
261 """
262 Store any bytes received
263
264 @param bytes: output from telnet
265 @type bytes: string
266 """
267 self.bytes += bytes
268
269
271 """
272 Start a timer to decide if we continue or not.
273
274 @param timeout: time in seconds to wait
275 @type timeout: integer
276 @param timeoutfunc: override for the default timeout timer
277 @type timeoutfunc: function
278 """
279 self.cancelTimeout()
280 if timeoutfunc is None: timeoutfunc = self.defaultTimeout
281 self.timeoutID = reactor.callLater(timeout, timeoutfunc)
282
283
290
291
303
304
305
327
328
353
354
356 """
357 Called when the password prompt is expected
358
359 @param data: data sent back from the remote device
360 @type data: string
361 @return: next state (Password, FindPrompt)
362 @rtype: string
363 """
364 log.debug('Search for password regex (%s) in (%s) finds: %r' % \
365 (self.factory.passwordRegex, data, \
366 re.search(self.factory.passwordRegex, data)))
367 if not re.search(self.factory.passwordRegex, data):
368 return 'Password'
369 log.debug("Sending password")
370 self.write(self.factory.password + '\n')
371 self.startTimeout(self.factory.promptTimeout)
372 return 'FindPrompt'
373
374
376 """
377 Switch to 'enable' mode on a Cisco device
378
379 @param unused: unused (unused)
380 @type unused: string
381 @return: next state (Password)
382 @rtype: string
383 """
384 self.write('enable\n')
385 self.startTimeout(self.factory.loginTimeout, self.loginTimeout)
386 return "EnablePassword"
387
388
390 """
391 Called when the enable password prompt is expected
392
393 @param data: data sent back from the remote device
394 @type data: string
395 @return: next state (EnablePassword, FindPrompt)
396 @rtype: string
397 """
398 log.debug('Search for enable password regex (%s) in (%s) finds: %r' % \
399 (self.factory.enableRegex, data, \
400 re.search(self.factory.enableRegex, data)))
401 if not re.search(self.factory.enableRegex, data):
402 return 'EnablePassword'
403
404
405 password = self.factory.enablePassword or self.factory.password
406
407 log.debug("Sending enable password")
408 self.write(password + '\n')
409 self.startTimeout(self.factory.promptTimeout)
410 return 'FindPrompt'
411
412
445
446
448 """
449 Called to try to restore sanity to output from the user.
450 Send an empty string to get back a prompt
451
452 @param unused: unused (unused)
453 @type unused: string
454 @return: next state (ClearPromptData)
455 @rtype: string
456 """
457 if self.scCallLater: self.scCallLater.cancel()
458 self.scCallLater = reactor.callLater(1.0, self.telnet_SendCommand, "")
459 return "ClearPromptData"
460
461
478
479
509
510
512 """
513 Return the current command to run
514
515 @return: next command to run
516 @rtype: string
517 """
518 return self.factory._commands[self.factory.cmdindex]
519
520
521
523 """
524 Reactor code to start communications and invoke our
525 telnet transport mechanism.
526 """
527
528 - def __init__(self, hostname, ip, port, plugins=[], options=None,
529 device=None, datacollector=None):
530 """
531 Initializer
532
533 @param hostname: hostname of the device
534 @type hostname: string
535 @param ip: IP address of the device
536 @type ip: string
537 @param port: port number to use to connect to device
538 @type port: integer
539 @param plugins: plugins
540 @type plugins: list of plugins
541 @param options: options
542 @type options: list
543 @param device: name of device
544 @type device: string
545 @param datacollector: object
546 @type datacollector: object
547 """
548 CollectorClient.CollectorClient.__init__(self, hostname, ip, port,
549 plugins, options, device, datacollector)
550 global defaultPromptTimeout
551 global defaultLoginRegex
552 global defaultPasswordRegex
553 global defaultEnable
554
555 self.protocol = TelnetClientProtocol
556 self.modeRegex = {
557 'FindPrompt' : '.*',
558 'WasteTime' : '.*',
559 'Done' : '',
560 }
561 self.promptPause = 1
562
563 if options:
564 defaultPromptTimeout = options.promptTimeout
565 defaultLoginRegex = options.loginRegex
566 defaultPasswordRegex = options.passwordRegex
567 defaultEnable = options.enable
568 defaultEnableRegex = options.enableRegex
569 defaultEnablePassword = options.enablePassword
570
571 if device:
572 self.promptTimeout = getattr(device,
573 'zTelnetPromptTimeout', defaultPromptTimeout)
574 self.loginRegex = getattr(device,
575 'zTelnetLoginRegex', defaultLoginRegex)
576 self.passwordRegex = getattr(device,
577 'zTelnetPasswordRegex', defaultPasswordRegex)
578 self.enable = getattr(device,
579 'zTelnetEnable', defaultEnable)
580 self.enableRegex = getattr(device,
581 'zTelnetEnableRegex', defaultEnableRegex)
582 self.enablePassword = getattr(device,
583 'zEnablePassword', defaultEnablePassword)
584 self.termlen = getattr(device,
585 'zTelnetTermLength', defaultTermLength)
586
587 else:
588 self.promptTimeout = defaultPromptTimeout
589 self.loginRegex = defaultLoginRegex
590 self.passwordRegex = defaultPasswordRegex
591 self.enable = defaultEnable
592 self.enableRegex = defaultEnableRegex
593 self.enablePassword = defaultEnablePassword
594 self.termlen = defaultTermLength
595
596 self.modeRegex['Login'] = self.loginRegex
597 self.modeRegex['Password'] = self.passwordRegex
598
599
601 """
602 Start telnet collection.
603 """
604 if self.termlen:
605
606 self._commands.insert(0, "terminal pager 0")
607
608
609 self._commands.insert(0, "terminal length 0")
610
611 reactor.connectTCP(self.ip, self.port, self)
612
613
615 """
616 Add new commands to be run reset cmdindex to 0
617
618 @param commands: commands to run on the remote device
619 @type commands: list of commands
620 """
621 CollectorClient.CollectorClient.addCommand(self, commands)
622 if self.myprotocol.mode != "Command":
623 self.myprotocol.telnet_SendCommand("")
624
625
627 """
628 If we don't connect let the modeler know
629
630 @param connector: unused (unused)
631 @type connector: unused
632 @param reason: error message to report
633 @type reason: string
634 """
635 unused(connector)
636 log.warn(reason.getErrorMessage())
637 self.clientFinished()
638
639
640
642 """
643 Command-line telnet options
644 """
645
646 parser = CollectorClient.buildOptions(parser,usage)
647
648 parser.add_option('-r', '--promptTimeout',
649 dest='promptTimeout',
650 type = 'float',
651 default = defaultPromptTimeout,
652 help='Timeout when discovering prompt')
653 parser.add_option('-x', '--loginRegex',
654 dest='loginRegex',
655 default = defaultLoginRegex,
656 help='Python regular expression that will find the login prompt')
657 parser.add_option('-w', '--passwordRegex',
658 dest='passwordRegex',
659 default = defaultPasswordRegex,
660 help='Python regex that will find the password prompt')
661 parser.add_option('--enable',
662 dest='enable', action='store_true', default=False,
663 help="Enter 'enable' mode on a Cisco device")
664 parser.add_option('--enableRegex',
665 dest='enableRegex',
666 default=defaultEnableRegex,
667 help='Python regex that will find the enable prompt')
668 parser.add_option('--enablePassword',
669 dest='enablePassword',
670 default=defaultEnablePassword,
671 help='Enable password')
672 parser.add_option('--termlen',
673 dest='termlen', action='store_true', default=False,
674 help="Enter 'send terminal length 0' on a Cisco device")
675 return parser
676
677
679 """
680 Fake class to provide plugin instances for command-line processing.
681 """
684
687
688
690 """
691 The TelntClient class expects plugins.
692 Convert commands like 'ls a', 'ls b' to plugin instances.
693 Duplicate commands will (eventually) be removed.
694 This is used to support command-line arguments.
695
696 @param commands: list of commands from command-line
697 @type commands: list of strings
698 @return: list of commands, plugin-style
699 @rtype: list of FakePlugins
700 """
701 return [ FakePlugin( cmd ) for cmd in commands ]
702
703
705 """
706 Test harness main()
707
708 Usage:
709
710 python TelnetClient.py hostname[:port] comand [command]
711
712 Each command must be enclosed in quotes (") to be interpreted
713 properly as a complete unit.
714 """
715 import socket
716 import getpass
717 import pprint
718
719 parser = buildOptions()
720 options = CollectorClient.parseOptions(parser, 23)
721 if not options.password:
722 options.password = getpass.getpass("%s@%s's password: " %
723 (options.username, options.hostname))
724 logging.basicConfig()
725 log.setLevel(options.logseverity)
726 commands = commandsToPlugins( options.commands )
727 client = TelnetClient(options.hostname,
728 socket.gethostbyname(options.hostname),
729 options.port,
730 plugins=commands, options=options)
731 client.run()
732 client.clientFinished= reactor.stop
733
734 reactor.run()
735
736 pprint.pprint(client.getResults())
737
738 if __name__ == '__main__':
739 main()
740