Module shellutils
[hide private]

Source Code for Module shellutils

   1  #coding=utf-8 
   2  """ 
   3  This library contains APIs for executing shell commands, for getting the exit status of 
   4  the executed command, and for allowing multiple commands to run conditional on that exit status. 
   5  It is initialized with a Shell Client, and then uses the client to run commands and get the results. 
   6  """ 
   7  from com.hp.ucmdb.discovery.probe.services.dynamic.core import DynamicServiceFrameworkImpl 
   8  from com.hp.ucmdb.discovery.library.communication.downloader.cfgfiles import GeneralSettingsConfigFile 
   9   
  10  import modeling 
  11  import netutils 
  12  import logger 
  13  import errorcodes 
  14  import errorobject 
  15  import shell_interpreter 
  16  import re 
  17  import string 
  18  import sys 
  19  import codecs 
  20   
  21  from java.io import File 
  22  from java.util import Properties, WeakHashMap 
  23  from java.util import Locale 
  24  from java.nio.charset import Charset 
  25  from java.nio import ByteBuffer 
  26  from java.nio.charset import CodingErrorAction 
  27   
  28  from com.hp.ucmdb.discovery.library.common import CollectorsParameters 
  29  from com.hp.ucmdb.discovery.library.clients.agents import NTCmdSessionAgent 
  30  from com.hp.ucmdb.discovery.library.clients.agents.ssh import SSHAgent 
  31  from com.hp.ucmdb.discovery.library.clients.agents import AgentConstants 
  32  from com.hp.ucmdb.discovery.library.clients.agents import PowerShellAgent 
  33  from com.hp.ucmdb.discovery.library.clients.shell import PowerShellClient 
  34  from com.hp.ucmdb.discovery.library.clients.protocols.telnet import StreamReaderThread 
  35   
  36  from java.lang import String 
  37  from java.lang import Boolean 
  38   
  39   
  40  DDM_LINK_SYSTEM32_LOCATION = "%SystemDrive%" 
  41  DDM_LINK_SYSTEM32_NAME = "ddm_link_system32" 
  42   
  43   
44 -class Command:
45 'Shell command base class'
46 - def __init__(self, line, output=None, returnCode=None):
47 '''@types: str, str, int -> None 48 @deprecated 49 ''' 50 self.cmd = line 51 52 self.line = line 53 self.output = output 54 self.outputInBytes = [] 55 self.returnCode = returnCode 56 self.executionTimeout = 0 57 self.waitForTimeout = 0 58 self.useSudo = 1 59 self.checkErrCode = 1 60 self.preserveSudoContext = 0
61
62 - def __repr__(self):
63 return self.line
64
65 - def __str__(self):
66 return self.line
67 68
69 -class Language:
70 r'''Language describes localized system configuration: locale, character sets, 71 code pages etc''' 72 DEFAULT_CHARSET_OBJECT = Charset.defaultCharset() 73
74 - def __init__(self, locale, bundlePostfix, charsets, wmiCodes=None, codepage=None):
75 ''' Default constructor also performs initialization of supported 76 character sets on the probe machine 77 @types: java.util.Locale, str, str, list(int), int -> None''' 78 self.locale = locale 79 self.bundlePostfix = bundlePostfix 80 self.charsets = charsets 81 self.wmiCodes = wmiCodes 82 self.charsetNameToObject = {} 83 self.__initCharsets() 84 self.codepage = codepage
85
86 - def __initCharsets(self):
87 'Find supported character sets on the probe machine' 88 for charsetName in self.charsets: 89 try: 90 charset = Charset.forName(charsetName) 91 self.charsetNameToObject[charsetName] = charset 92 except: 93 logger.warn("Charset is not supported: %s." % charsetName)
94 95 LOCALE_SPANISH = Locale("es", "", "") 96 LOCALE_RUSSIAN = Locale("ru", "", "") 97 LOCALE_GERMAN = Locale("de", "", "") 98 LOCALE_FRENCH = Locale("fr", "", "") 99 LOCALE_ITALIAN = Locale("it", "", "") 100 LOCALE_PORTUGUESE = Locale("pt", "", "") 101 LOCALE_CHINESE = Locale("cn", "", "") 102 LOCALE_KOREAN = Locale("kr", "", "") 103 LOCALE_DUTCH = Locale("nl", "", "") 104 105 LANG_ENGLISH = Language(Locale.ENGLISH, 'eng', ('Cp1252',), (1033,), 437) 106 LANG_GERMAN = Language(Locale.GERMAN, 'ger', ('Cp1252', 'Cp850'), (1031,), 850) 107 LANG_SPANISH = Language(LOCALE_SPANISH, 'spa', ('Cp1252',), (1034, 3082,)) 108 LANG_RUSSIAN = Language(LOCALE_RUSSIAN, 'rus', ('Cp866', 'Cp1251'), (1049,), 866) 109 LANG_JAPANESE = Language(Locale.JAPANESE, 'jap', ('MS932',), (1041,), 932) 110 LANG_FRENCH = Language(Locale.FRENCH, 'fra', ('Cp1252',), (1036,), 850) 111 LANG_ITALIAN = Language(Locale.ITALIAN, 'ita', ('Cp1252',), (1040,), 850) 112 LANG_DUTCH = Language(LOCALE_DUTCH, 'nld', ('Cp1252',), (1043,), 850) 113 LANG_PORTUGUESE = Language(LOCALE_PORTUGUESE, 'prt', ('Cp1252',), (1046,), 850) 114 LANG_CHINESE = Language(Locale.CHINESE, 'chn', ('MS936',), (2052,), 936) 115 LANG_KOREAN = Language(Locale.KOREAN, 'kor', ('MS949',), (1042,), 949) 116 117 LANGUAGES = (LANG_ENGLISH, LANG_GERMAN, LANG_SPANISH, LANG_RUSSIAN, 118 LANG_JAPANESE, LANG_FRENCH, LANG_ITALIAN, LANG_PORTUGUESE, 119 LANG_CHINESE, LANG_KOREAN, LANG_DUTCH) 120 121 #Used as default language for fallback 122 DEFAULT_LANGUAGE = LANG_ENGLISH 123 124
125 -class OsLanguageDiscoverer:
126 'Discoverer determines language on destination system'
127 - def __init__(self, shell):
128 '@types: Shell -> None' 129 self.shell = shell
130
131 - def getLanguage(self):
132 '@types: -> Language' 133 raise NotImplementedError()
134 135
136 -class WindowsLanguageDiscoverer(OsLanguageDiscoverer):
137 'Windows specific discoverer'
138 - def __init__(self, shell):
139 '@types: Shell -> None' 140 OsLanguageDiscoverer.__init__(self, shell)
141
142 - def getLanguage(self):
143 ''' Determine language for Windows system. 144 @types: -> Language 145 @note: English will be used as default one if fails 146 to determine machine encoding 147 ''' 148 language = self.getLanguageFromWmi() 149 if not language: 150 language = self.getLanguageFromChcp() 151 if not language: 152 logger.warn('Failed to determine machine encoding, using default %s' % DEFAULT_LANGUAGE.locale) 153 return DEFAULT_LANGUAGE 154 else: 155 return language
156
158 '''@types: -> shellutils.Language or None 159 @command: wmic OS Get CodeSet 160 ''' 161 osLanguageOutput = self.shell.execAlternateCmds('wmic OS Get CodeSet < %SystemRoot%\win.ini', '%WINDIR%\system32\wbem\wmic OS Get CodeSet < %SystemRoot%\win.ini') 162 if osLanguageOutput and self.shell.getLastCmdReturnCode() == 0: 163 return self.__parseLanguageFromWmi(osLanguageOutput)
164
166 '''@types: -> shellutils.Language or None 167 @command: wmic OS Get OSLanguage 168 ''' 169 osLanguageOutput = self.shell.execAlternateCmds('wmic OS Get OSLanguage < %SystemRoot%\win.ini', '%WINDIR%\system32\wbem\wmic OS Get OSLanguage < %SystemRoot%\win.ini') 170 if osLanguageOutput and self.shell.getLastCmdReturnCode() == 0: 171 return self.__parseLanguageFromWmi(osLanguageOutput)
172
173 - def __parseLanguageFromWmi(self, osLanguageOutput):
174 '@types: str -> shellutils.Language or None' 175 if osLanguageOutput: 176 languageResult = None 177 matcher = re.search(r"(\d{4})", osLanguageOutput) 178 if matcher: 179 osLanguageCodeStr = matcher.group(1) 180 osLanguageCode = int(osLanguageCodeStr) 181 for lang in LANGUAGES: 182 if osLanguageCode in lang.wmiCodes: 183 logger.debug('Bundle postfix %s' % lang.bundlePostfix) 184 languageResult = lang 185 break 186 return languageResult
187
188 - def getLanguageFromWmi(self):
189 ''' Determine language executing WMI commands 190 @types: -> Language or None''' 191 return (self.__getLanguageFromWmiUsingCodeSet() 192 or self.__getLanguageFromWmiUsingOsLanguage())
193
194 - def getLanguageFromChcp(self):
195 ''' Determine language executing chcp command 196 @types: -> Language or None 197 @command: chcp 198 ''' 199 languageResult = None 200 codepage = int(self.shell.getCodePage()) 201 for lang in LANGUAGES: 202 if codepage == lang.codepage: 203 logger.debug('Bundle postfix %s' % lang.bundlePostfix) 204 languageResult = lang 205 break 206 return languageResult
207 208
209 -class UnixLanguageDiscoverer(OsLanguageDiscoverer):
210 'For Unix destination localization is not supported. English language is used'
211 - def __init__(self, shell):
212 '@types: Shell -> None' 213 OsLanguageDiscoverer.__init__(self, shell)
214
215 - def getLanguage(self):
216 '@types: -> Language' 217 return DEFAULT_LANGUAGE
218 219
220 -class OutputMatcher:
221 - def match(self, content):
222 '@types: str -> bool' 223 raise NotImplementedError()
224 225
226 -class KeywordOutputMatcher(OutputMatcher):
227 - def __init__(self, keyword):
228 '@types: str -> None' 229 self.keyword = keyword
230
231 - def match(self, content):
232 '@types: str -> bool' 233 logger.debug('Matching by keyword: %s' % self.keyword) 234 #logger.debug('Content: %s' % content) 235 if self.keyword and content: 236 buffer = re.search(self.keyword, content, re.I) 237 if buffer: 238 return 1
239 240
241 -class EncodingContext:
242
243 - def __init__(self, bytesArray, language, framework):
244 '@types: jarray(byte), Language, Framework' 245 self.bytesArray = bytesArray 246 self.language = language 247 self.framework = framework 248 self.outputHandlers = []
249
250 - def addOutputHandler(self, handler):
251 '''@types: OutputHandler -> None''' 252 self.outputHandlers.append(handler)
253
254 - def getDecodedString(self, outputMatcher):
255 ''' Select such charset that decoded content using it will match using matcher 256 and return decoded content 257 @types: OutputMatcher -> tuple(str(decoded output), str(charset name))''' 258 charsetObjects = self.language.charsetNameToObject.values() 259 if charsetObjects: 260 for charset in charsetObjects: 261 decodedContent = self.decodeString(charset) 262 for outputHandler in self.outputHandlers: 263 decodedContent = outputHandler.handle(decodedContent) 264 265 decodedContentString = str(String(decodedContent)) 266 if outputMatcher.match(decodedContentString): 267 return (decodedContentString, charset.name()) 268 269 msg = "Command output verification has failed. Check whether the language is supported." 270 errobj = errorobject.createError(errorcodes.COMMAND_OUTPUT_VERIFICATION_FAILED, None, msg) 271 logger.reportWarningObject(errobj) 272 logger.debug(msg) 273 274 charset = Language.DEFAULT_CHARSET_OBJECT 275 decodedContent = str(String(self.decodeString(charset))) 276 return (decodedContent, charset.name())
277
278 - def decodeString(self, charsetObject):
279 ''' Decode string with specified charset 280 @types: java.nio.charset.Charset -> jarray(char)''' 281 decoder = charsetObject.newDecoder() 282 decoder.onMalformedInput(CodingErrorAction.REPLACE) 283 decoder.onUnmappableCharacter(CodingErrorAction.REPLACE) 284 charBuffer = decoder.decode(ByteBuffer.wrap(self.bytesArray)) 285 return charBuffer.array()
286 287
288 -class OutputHandler:
289 - def handle(self, contents):
290 '@types: jarray(char) -> jarray(char)' 291 return contents
292 293
294 -class SSHOutputHandler(OutputHandler):
295 - def handle(self, contents):
296 '@types: jarray(char) -> str' 297 resultContents = self.translateBackspaces(contents) 298 resultContents = self.removeMarkers(resultContents) 299 try: 300 resultContents, code = _extractCommandOutputAndReturnCode(resultContents) #@UnusedVariable 301 except ValueError, e: 302 logger.debug(str(e)) 303 return resultContents
304
305 - def translateBackspaces(self, contents):
306 '@types: jarray(char) -> str' 307 return SSHAgent.translateBackspace(String(contents))
308
309 - def removeMarkers(self, contents):
310 '@types: str -> str' 311 return SSHAgent.removeMarkers(contents)
312 313
314 -class PowerShellOutputHandler(OutputHandler):
315 POWERSHELL_CMD_SUCCESS = 'True' 316 POWERSHELL_CMD_FAILED = 'False' 317
318 - def __init__(self, command=None, trimWS=None):
319 '@types: str, bool' 320 self.command = command 321 self.trimWS = trimWS
322
323 - def isCommandSucceeded(self, output):
324 ''' Using status markers in output define command execution status 325 @types: str -> bool 326 @raise ValueError: Unknown command status 327 ''' 328 output = output.strip() 329 if output.endswith(PowerShellOutputHandler.POWERSHELL_CMD_SUCCESS): 330 return 0 331 elif output.endswith(PowerShellOutputHandler.POWERSHELL_CMD_FAILED): 332 return 1 333 raise ValueError('Unknown command status')
334
335 - def cleanOutput(self, output, isFailed):
336 ''' Clean output from command execution status marker 337 @types: str, int -> str 338 ''' 339 if not output or isFailed < 0: 340 return output 341 status = (isFailed 342 and PowerShellOutputHandler.POWERSHELL_CMD_FAILED 343 or PowerShellOutputHandler.POWERSHELL_CMD_SUCCESS) 344 index = output.rfind(status) 345 if index == 0: 346 return '' 347 if index < 0: 348 return output 349 # remove status with new line 350 return output[0:index - 1]
351
352 - def handle(self, contentChars):
353 '''@types: jarray(char) -> jarray(char)''' 354 cleanedOutput = PowerShellAgent.cleanupCommandOutput(self.command, String(contentChars), self.trimWS) 355 return String(cleanedOutput).toCharArray()
356 357
358 -class NTCMDOutputHandler(OutputHandler):
359 - def __init__(self, command, trimWS):
360 '@types: str, bool' 361 self.command = command 362 self.trimWS = trimWS
363
364 - def handle(self, contentChars):
365 '@types: jarray(char) -> jarray(char)' 366 cleanedOutput = NTCmdSessionAgent.cleanupCommandOutput(self.command, String(contentChars), self.trimWS) 367 return String(cleanedOutput).toCharArray()
368 369
370 -class TelnetOutputHandler(OutputHandler):
371 - def handle(self, contents):
372 '@types: jarray(char) -> jarray(char)' 373 return StreamReaderThread.translateBackspace(contents, len(contents)).toCharArray()
374 375
376 -class __CmdTransaction:
377 ''' Command transaction class. 378 Provides possibility to define alternative ways for determining command execution status 379 '''
380 - def __init__(self, cmd, expectedReturnCode=None, sucessString=None, failureString=None):
381 '''@types: str, int, str, str''' 382 self.cmd = cmd 383 self.expectedReturnCode = expectedReturnCode 384 self.sucessString = sucessString 385 self.failureString = failureString
386 387
388 -def ShellUtils(client, props=None, protocolName=None):
389 '''BaseClient or DynamicServiceFrameworkImpl, java.util.Properties, str -> Shell 390 This method play role of factory method for backward compatibility and will be removed in further releases 391 @deprecated: Use ShellFactory instead 392 @raise Exception: failed to detect OS 393 ''' 394 if isinstance(client, DynamicServiceFrameworkImpl): 395 if not props: 396 props = Properties() 397 Framework = client 398 client = Framework.createClient(props) 399 #TODO (stage 2) remove protocol name (it is possible when getOsType method will be migrated too) 400 return ShellFactory().createShell(client, protocolName)
401 402
403 -class ShellFactory:
404 '''This class manages shell creation for different shell protocols SSH, Telnet, or NTCMD.'''
405 - def createShell(self, client, protocolName=None):
406 '''@types: Client, java.util.Properties, str -> Shell 407 @raise Exception: failed to detect OS 408 ''' 409 protocolName = protocolName or client.getClientType() 410 if protocolName == PowerShell.PROTOCOL_TYPE: 411 logger.debug('Windows powershell detected') 412 shell = PowerShell(client, protocolName) 413 elif self.isWinOs(client): 414 logger.debug('Windows ntcmd detected') 415 shell = WinShell(client, protocolName) 416 elif self.isCygwin(client): 417 logger.debug('Cygwin detected') 418 shell = CygwinShell(client, protocolName) 419 elif self.isVioServer(client): 420 logger.debug('IBM VIO Server detected') 421 shell = VIOShell(client) 422 elif self.isMacOs(client): 423 logger.debug('MacOs detected') 424 shell = MacShell(client) 425 else: 426 logger.debug('Unix detected') 427 shell = UnixShell(client) 428 429 return shell
430
431 - def isCygwin(self, shellClient):
432 ''' Check for cygwin shell. In cygwin shell it is possible to launch 433 windows command interpreter. 434 @types: Client -> bool 435 @command: cmd.exe /c ver 436 @raise Exception: Failed starting Windows Cmd Shell 437 ''' 438 winOs = 0 439 logger.debug('Check for Cygwin installed on Windows') 440 buffer = shellClient.executeCmd('cmd.exe /c ver') 441 if self.__isWinDetectInVerOutput(buffer): 442 try: 443 logger.debug('Entering Windows CMD shell') 444 shellClient.executeCmd('cmd.exe', 10000, 1) 445 shellClient.getShellCmdSeperator() 446 except: 447 raise Exception("Failed starting Windows Cmd Shell") 448 winOs = 1 449 return winOs
450
451 - def isWinOs(self, shellClient):
452 ''' Determine windows shell trying to execute 'ver' command 453 @command: ver 454 @types: BaseClient -> bool 455 @raise Exception: Failed detecting OS type 456 ''' 457 try: 458 osBuff = shellClient.executeCmd('ver') 459 if osBuff is None: 460 errMsg = 'Failed detecting OS type. command=\'ver\' returned with no output' 461 logger.error(errMsg) 462 raise Exception(errMsg) 463 #83101: sometimes ntcmd runs into shell with 'MS-DOS 5.00.500' version 464 #'MS-DOS' check prevents the job going the 'Unix' path 465 winOs = self.__isWinDetectInVerOutput(osBuff) 466 return winOs 467 except: 468 errMsg = 'Failed detecting OS type. Exception received: %s' % (sys.exc_info()[1]) 469 logger.error(errMsg) 470 raise Exception(errMsg)
471
472 - def isVioServer(self, shellClient):
473 '''Determine restricted shell of VIO server installed on AIX 474 @command: ioscli uname -a 475 @types: BaseClient -> bool''' 476 logger.debug("Check for IBM VIO Server Shell") 477 isVioServer = 0 478 try: 479 output = shellClient.executeCmd("ioscli uname -a") 480 if not output or output.count("ioscli"): 481 logger.debug('Failed detecting IBM VIO Server. %s' % output) 482 else: 483 isVioServer = 1 484 except: 485 logger.warnException('Failed detecting IBM VIO Server') 486 return isVioServer
487
488 - def isMacOs(self, shellClient):
489 try: 490 osBuff = shellClient.executeCmd('uname') 491 if osBuff and re.match("\s*Darwin", osBuff): 492 return 1 493 except: 494 logger.warn('Failed checking for MacOS') 495 logger.debugException('')
496
497 - def __isWinDetectInVerOutput(self, buffer):
498 '@types: str -> bool' 499 return (buffer 500 and (buffer.lower().find('windows') > -1 501 or buffer.find('MS-DOS') > -1))
502 503
504 -class CommandNotFound(Exception):
505 'Exception case when command does not exists or cannot be recognized' 506 pass
507 508
509 -class NoPermission(Exception):
510 'Exception case when client has no permission to execute command' 511 pass
512 513
514 -class Shell:
515 """ 516 Class for managing Shell connections via SSH, Telnet, or NTCmd 517 518 B{Shell is not thread-safe.} Every thread must have its own instance. 519 """ 520 # class static constants 521 NO_CMD_RETURN_CODE_ERR_NUMBER = -9999 # constant to set last command error code when no error code could be retrieved from shell 522
523 - def __init__(self, client):
524 '@types: Client' 525 self.cmdCache = WeakHashMap() 526 # class instance data members 527 self.__client = client # keep client connection object 528 #@deprecated: will be removed from public access 529 self.osType = None # operating system of the current connection session 530 #@deprecated: will be removed from public access 531 self.osVer = None # operating system version of the current connection session 532 self.__lastCmdReturnCode = None # terminate status of the last command executed 533 self.__alternateCmdList = [] # list of alternative command 534 self.__shellCmdSeparator = None 535 536 #@deprecated: will be removed from public access 537 self.winOs = self.isWinOs() 538 self.getOsType() 539 #@deprecated: will be removed from public access 540 self.getLastCommandOutputBytes = None 541 #@deprecated: will be removed from public access 542 self.lastExecutedCommand = None 543 #@deprecated: will be removed from public access 544 self.copiedFiles = [] 545 546 self.osLanguage = None 547 #@deprecated: will be removed from public access 548 self.charsetName = None 549 self.determineOsLanguage() 550 551 if self.osLanguage.charsets: 552 self.useCharset(self.osLanguage.charsets[0]) 553 #@deprecated: will be removed from public access 554 self.globalSettings = GeneralSettingsConfigFile.getInstance() 555 self.__defaultCommandTimeout = None 556 self.getDefaultCommandTimeout()
557
558 - def getDefaultCommandTimeout(self):
559 ''' Get default command timeout declared in globalSettings.xml 560 @types: -> number 561 ''' 562 if not self.__defaultCommandTimeout: 563 self.__defaultCommandTimeout = self.globalSettings.getPropertyIntegerValue('shellGlobalCommandTimeout', 1) 564 return self.__defaultCommandTimeout
565
566 - def setSessionLocale(self):
567 '@deprecated: Unix only dedicated' 568 pass
569
570 - def isWinOs(self):
571 '@types: -> bool' 572 return NotImplementedError()
573
574 - def getOsType(self):
575 """Returns the type of the operating system running on the target host to which the shell client is connected 576 @types: -> str 577 @raise Exception: Failed getting machine OS type 578 """ 579 if not self.osType: 580 try: 581 self.osType = self._getOsType() 582 except Exception, e: 583 logger.errorException(str(e)) 584 raise Exception('Failed getting machine OS type') 585 return self.osType
586
587 - def _getOsType(self):
588 ''' Template method for each derived class to get properly OS type 589 @types: -> str 590 @raise Exception: Failed getting machine OS type''' 591 return NotImplementedError()
592
593 - def isWindowsWithCygwin(self):
594 ''' Indicates whether running commands in cygwin shell 595 @types: -> bool 596 @deprecated: will be removed from public access''' 597 return 0
598
599 - def getOsVersion(self):
600 """ Get version of the OS running on the target host to which the shell 601 client is connected 602 @types: -> str or None 603 @deprecated: should be moved to the OS discoverer (domain layer) 604 """ 605 if not self.osVer: 606 try: 607 self.osVer = self._getOsVersion() 608 except Exception, e: 609 logger.warn(str(e)) 610 return self.osVer
611
612 - def getOsLanguage(self):
613 return self.osLanguage
614
615 - def determineOsLanguage(self):
616 "@deprecated: will be removed from the public access" 617 self.osLanguage = self._getOsLanguageDiscoverer().getLanguage()
618
619 - def _getOsVersion(self):
620 ''' Template method to get OS version for derived shell 621 @types: -> str 622 @raise Exception: Failed to get OS version 623 ''' 624 return NotImplemented
625
626 - def _getOsLanguageDiscoverer(self):
627 '@types: -> OsLanguageDiscoverer' 628 raise NotImplemented
629
630 - def useCharset(self, charsetName):
631 ''' Use specified character set for command encoding and output decoding in raw client 632 @types: str 633 @raise IllegalCharsetNameException: The given charset name is illegal 634 @raise UnsupportedCharsetException: No support for the named charset is available in this instance of the JVM 635 ''' 636 charset = Charset.forName(charsetName) 637 logger.debug('Using charset: %s' % charsetName) 638 logger.debug('Can encode: %s' % charset.canEncode()) 639 self.__client.setCharset(charset)
640
641 - def getShellStatusVar(self):
642 """ Returns the shell command exit status code 643 @types: -> str 644 """ 645 raise NotImplemented
646
647 - def getCommandSeparator(self):
648 ''' Get command separator depending on OS type 649 @types: -> str''' 650 return self.getShellCmdSeperator()
651
652 - def getShellCmdSeperator(self):
653 """ 654 Returns the shell command separator character. 655 This is the character used between commands when more than one command 656 is passed on the same command line. 657 @types: -> str 658 @deprecated: Use getCommandSeparator instead 659 """ 660 if (self.__shellCmdSeparator is None): 661 self.__shellCmdSeparator = self.__client.getShellCmdSeperator() 662 return self.__shellCmdSeparator
663
664 - def __addAlternateCmd(self, cmd, expectedReturnCode=None, sucessString=None, failureString=None):
665 '''Add alternative command to execute list 666 @types: str, int, str, str''' 667 self.__alternateCmdList.append(__CmdTransaction(cmd, expectedReturnCode, sucessString, failureString))
668
669 - def __removeAllAlternateCmds(self):
670 if (len(self.__alternateCmdList) > 0): 671 self.__alternateCmdList = []
672
673 - def __execCmdSet(self, timeout=0):
674 ''' Go over all alternative commands list and execute each until the first successful command termination. 675 This method basically executes shell commands and retrieves terminate status of the executed command 676 until a command ended successfully. 677 command is successful when: 678 1. it returned the expected return code. 679 2. if no return code is given, the output of the command is compared against sucessString 680 to determine command success. 681 3. if failureString is given the output of the command is compared against failureString 682 to determine command failure 683 @types: int -> str 684 ''' 685 err_output = '' 686 for cmdTrans in self.__alternateCmdList: 687 try: 688 output = self.execCmd(cmdTrans.cmd, timeout)#@@CMD_PERMISION shell protocol execution 689 if (cmdTrans.expectedReturnCode is not None): 690 if (cmdTrans.expectedReturnCode == self.getLastCmdReturnCode()): 691 logger.debug('command=\'%s\' ended successfully' % cmdTrans.cmd) 692 return output 693 else: 694 err_output = '%s\n%s' % (err_output, output) 695 logger.debug('command=%s did not pass return code creteria. got rc=%d expected rc=%d' % (cmdTrans.cmd, self.getLastCmdReturnCode(), cmdTrans.expectedReturnCode)) 696 continue 697 elif(cmdTrans.sucessString is not None): 698 if (output.find(cmdTrans.sucessString) > -1): 699 # success string was found found in output therfore command ended successfully 700 logger.debug('command=\'%s\' ended successfully' % cmdTrans.cmd) 701 return output 702 else: 703 err_output = '%s\n%s' % (err_output, output) 704 logger.debug('command=%s did not pass sucsess string creteria. got output string=%s expected=%s' % (cmdTrans.cmd, output, cmdTrans.sucessString)) 705 continue 706 elif(cmdTrans.failureString is not None): 707 if (output.find(cmdTrans.sucessString) > -1): 708 # failure string was found in output therefore command has failed 709 logger.debug('command=\'%s\' did not pass failure string creteria. got output string=%s which contains failure string=%s' % (cmdTrans.cmd, output, cmdTrans.failureString)) 710 err_output = '%s\n%s' % (err_output, output) 711 continue 712 else: 713 # failure string was not found therfore command ended successfully 714 logger.debug('command=\'%s\' ended successfully' % cmdTrans.cmd) 715 return output 716 except: 717 logger.warnException('Failed to execute %s.' % cmdTrans.cmd) 718 return err_output
719
720 - def execAlternateCmds(self, *commands):
721 """ 722 Executes the input list of shell commands until one of them succeeds. 723 @types: vararg(str) -> str or None 724 @return: output of the first successful command, otherwise None 725 """ 726 return self.execAlternateCmdsList(commands)
727
728 - def execAlternateCmdsList(self, commands, timeout=0):
729 """ 730 Executes the input list of shell commands until one of them succeeds. 731 @types: list(str), int -> str or None 732 @param timeout: timeout for each command in milliseconds 733 @return: output of the first successesfull command, otherwise None 734 """ 735 for cmd in commands: 736 logger.debug('adding alternate cmd=\'%s\'' % cmd) 737 self.__addAlternateCmd(cmd, 0) 738 output = self.__execCmdSet(timeout) 739 self.__removeAllAlternateCmds() 740 return output
741
742 - def getLastCmdReturnCode(self):
743 """ 744 Returns the exit status of the last shell command issued by execCmd or execAlternateCmds methods 745 @types: -> int 746 @return: exit status of the last command issued 747 @raise Exception: Cannot get last command return code: no command was issued 748 """ 749 if (self.__lastCmdReturnCode is None): 750 raise Exception('Cannot get last command return code: no command was issued') 751 return self.__lastCmdReturnCode
752
753 - def fsObjectExists(self, path):
754 """ Checks whether the file or directory exists on the File System 755 @types: str -> bool 756 @deprecated: Use FileSystem methods instead 757 """ 758 raise NotImplemented
759
760 - def execCmd(self, cmdLine, timeout=0, waitForTimeout=0, useSudo=1, checkErrCode=1, useCache=0, preserveSudoContext=0):
761 """ Executes a shell command and sets the exit status of the command. 762 @types: str, int, int, bool, bool, bool -> str 763 Issue the given command followed by an echo of its return status, in the last line of the output 764 The exit status is an integer. 765 @param cmdLine: command to execute 766 @param timeout: time in ms or if < 1ms treated as coefficient for predefined timeout 767 @return: output of the executed shell command 768 @raise Exception: Command execution does not produced output nor return code 769 """ 770 #We should keep this cache in client class(in java part or in the jython wrapper to the client) 771 if useCache and self.cmdCache.containsKey(cmdLine): 772 command = self.cmdCache.get(cmdLine) 773 self.__lastCmdReturnCode = command.returnCode 774 self.getLastCommandOutputBytes = command.outputInBytes 775 return command.output 776 777 if timeout and timeout < 1000: 778 timeout = timeout * self.__defaultCommandTimeout 779 780 command = Command(cmdLine) 781 command.executionTimeout = timeout 782 command.waitForTimeout = waitForTimeout 783 command.useSudo = useSudo 784 command.checkErrCode = checkErrCode 785 command.preserveSudoContext = preserveSudoContext 786 787 command = self._execute(command) 788 789 self.__lastCmdReturnCode = command.returnCode 790 self.getLastCommandOutputBytes = command.outputInBytes 791 792 self.cmdCache.put(cmdLine, command) 793 return command.output
794
795 - def _execute(self, command):
796 ''' Template method for derived shells for exact command execution 797 @types: Command -> Command 798 @raise Exception 799 @raise NoPermission 800 @raise CommandNotFound 801 ''' 802 raise NotImplemented
803
804 - def execCmdAsBytes(self, cmdLine, timeout=0, waitForTimeout=0, useSudo=1):
805 ''' Get raw data as list of bytes. 806 @types: str, int, int, bool -> list(byte)''' 807 self.execCmd(cmdLine, timeout, waitForTimeout, useSudo) 808 return self.getLastCommandOutputBytes
809
810 - def resolveHost(self, hostName, nodeClassName='node'):
811 """ Create host CI with IP as key resolved by host name 812 @types: str -> ObjectStateHolder or None 813 @return: IP address or None if IP cannot be resolved or is local 814 @deprecated: Will be removed in further version 815 """ 816 node_ip = None 817 dns_name = None 818 try: 819 node_ip = netutils.getHostAddress(hostName, None) 820 except: 821 node_ip = None 822 if (node_ip is None) or netutils.isLocalIp(node_ip): 823 try: 824 logger.debug('Trying to resolve ip for node %s by nslookup command' % hostName) 825 result = self.execCmd('nslookup %s' % hostName) 826 logger.debug('nslookup command returned result:', result) 827 m = re.search('(Name:)\s+(.+)\s+(Address:)\s+(\d+\.\d+\.\d+\.\d+)', result) or re.search('(Name:)\s+(.+)\s+(Addresses:)\s+(?:[0-9a-f:]*)\s+(\d+\.\d+\.\d+\.\d+)', result) 828 if m is not None: 829 node_ip = m.group(4).strip() 830 dns_name = m.group(2).strip() 831 else: 832 node_ip = None 833 except: 834 logger.debugException('Failed to resolve ip address of cluster node ', hostName) 835 node_ip = None 836 if (node_ip is None) or netutils.isLocalIp(node_ip): 837 return None 838 nodeHostOSH = modeling.createHostOSH(node_ip, nodeClassName) 839 if dns_name: 840 nodeHostOSH.setStringAttribute('host_dnsname', dns_name) 841 if hostName and not hostName.count('.'): 842 nodeHostOSH.setStringAttribute('host_hostname', hostName) 843 return nodeHostOSH
844
845 - def resolveIp(self, hostName):
846 """ Resolve IP by host name 847 @types: str -> str or None 848 @return: IP address or None if IP cannot be resolved or is local 849 @deprecated: Use netutils.IpResolver instead 850 """ 851 node_ip = None 852 try: 853 node_ip = netutils.getHostAddress(hostName, None) 854 except: 855 node_ip = None 856 if (node_ip is None) or (netutils.isLocalIp(node_ip)): 857 try: 858 logger.debug('Trying to resolve ip for node %s by nslookup command' % hostName) 859 result = self.execCmd('nslookup %s' % hostName)#@@CMD_PERMISION shell protocol execution 860 logger.debug('nslookup command returned result:', result) 861 m = re.search('(Name:)\s+(.+)\s+(Address:)\s+(\d+\.\d+\.\d+\.\d+)', result) or re.search('(Name:)\s+(.+)\s+(Addresses:)\s+(?:[0-9a-f:]*)\s+(\d+\.\d+\.\d+\.\d+)', result) 862 if m is not None: 863 node_ip = m.group(4).strip() 864 else: 865 node_ip = None 866 except: 867 logger.debugException('Failed to resolve ip address of cluster node ', hostName) 868 node_ip = None 869 if (node_ip is None) or (netutils.isLocalIp(node_ip)): 870 return None 871 return node_ip
872
873 - def getXML(self, path, forceSudo=0):
874 ''' Get xml content with proper encoding 875 @types: str, bool -> str 876 @param forceSudo:if true, always uses sudo, if false first tries to run command without sudo 877 @deprecated: Use file system module for such purposes 878 ''' 879 content = self.safecat(path, forceSudo) 880 if content: 881 match = re.search(r'encoding="(\S+.)"', content) 882 if match: 883 encoding = match.group(1) 884 encodedContent = String(self.getLastCommandOutputBytes, encoding) 885 handler = self.createOutputHandler(self.lastExecutedCommand) 886 output = handler.handle(encodedContent) 887 return str(String(output)) 888 return content
889
890 - def safecat(self, path, forceSudo=0):
891 ''' Get file content by specified path 892 @types: str, bool -> str 893 @deprecated: Use methods from file system module instead 894 @param path:full path (including name) to the desired file 895 @param forceSudo:if true, always uses sudo, if false first tries to run command without sudo 896 @raise Exception: Redirection symbols used or getting content failed 897 ''' 898 raise NotImplemented
899
900 - def getClientType(self):
901 '@types: -> str' 902 # TODO: return enum ! 903 return self.__client.getClientType()
904
905 - def getPort(self):
906 '@types: -> int' 907 return self.__client.getPort()
908
909 - def getCredentialId(self):
910 '@types: -> str' 911 return self.__client.getCredentialId()
912
913 - def closeClient(self):
914 '''Perform cleaning of temporary data on destination system and close the client''' 915 self.__removeCopiedData() 916 self.__client and self.__client.close()
917
918 - def __removeCopiedData(self):
919 removeCopiedData = self.globalSettings.getPropertyStringValue('removeCopiedFiles', 'true') 920 if removeCopiedData and removeCopiedData.lower() == 'true': 921 for remoteFile in self.copiedFiles: 922 self.deleteFile(remoteFile)
923
924 - def canCopyFile(self):
925 '''Indicates whether client can copy files to the remote system 926 @types: -> bool''' 927 return self.__client.canCopyFile()
928
929 - def rebuildPath(self, folder):
930 ''' Normalize path to one used in OS client connected to 931 @types: str -> str 932 @deprecated: Use methods from file system module instead 933 ''' 934 raise NotImplemented
935
936 - def createOutputHandler(self, cmd=None):
937 ''' Create output handler depending OS type 938 @types: str -> OutputHandler 939 @deprecated: Will be moved to the private scope 940 @raise ValueError: If decoding is not supported for used protocol type 941 ''' 942 if self.getClientType() == 'ntadmin': 943 trimWSString = self.__client.getProperty(AgentConstants.PROP_NTCMD_AGENT_TRIM_WHITESPACE) 944 trimWS = 1 945 946 if trimWSString: 947 trimWS = Boolean.parseBoolean(trimWSString) 948 949 return NTCMDOutputHandler(cmd, trimWS) 950 elif self.getClientType() == 'ssh': 951 return SSHOutputHandler() 952 elif self.getClientType() == 'telnet': 953 return TelnetOutputHandler() 954 elif self.getClientType() == PowerShell.PROTOCOL_TYPE: 955 return PowerShellOutputHandler(cmd, 1) 956 else: 957 raise ValueError("Decoding is not supported for protocol type %s" % self.getClientType())
958
959 - def executeCommandAndDecodeByMatcher(self, cmd, matcher, framework, timeout=0, waitForTimeout=0, useSudo=1, language=None):
960 ''' Execute command and try to decode output using predefined charsets. 961 Decoded output considered as valid if it is matched by provided matcher. 962 @types: str, OutputMatcher, Framework, int, int, bool, Language -> str 963 @param matcher: defines matching case for valid character set 964 ''' 965 language = language or self.osLanguage 966 967 commandBytes = self.execCmdAsBytes(cmd, timeout, waitForTimeout, useSudo) 968 encodingContext = EncodingContext(commandBytes, language, framework) 969 970 encodingContext.addOutputHandler(self.createOutputHandler(cmd)) 971 972 (result, charsetName) = encodingContext.getDecodedString(matcher) 973 self.charsetName = charsetName 974 return result
975
976 - def executeCommandAndDecode(self, cmd, keyword, framework, timeout=0, waitForTimeout=0, useSudo=1, language=None):
977 ''' Execute command and try to decode output using predefined charsets. 978 Decoded output considered as valid if it is matched by KEYWORD matcher. 979 @types: str, str, Framework, int, int, bool, Language -> str''' 980 return self.executeCommandAndDecodeByMatcher(cmd, KeywordOutputMatcher(keyword), framework, timeout, waitForTimeout, useSudo, language)
981
982 - def getCharsetName(self):
983 ''' Get name of the character set for the latest command execution 984 @types: -> str or None 985 ''' 986 return self.charsetName
987 988
989 -class WinShell(Shell):
990 'Basic class for Windows shell' 991 #'@deprecated: Constant will be removed from the public access' 992 DEFAULT_ENGLISH_CODEPAGE = 437 993 DEFAULT_WIN_SHARE = 'admin$\\system32\\drivers\etc' 994 __DEFAULT_COMMAND_SEPARATOR = '&' 995
996 - def __init__(self, client, protocolName):
997 ''' @types: Client, str''' 998 self.__shellStatusVar = '%ERRORLEVEL%' 999 self.__protocolName = protocolName 1000 self.__osLanguageDiscoverer = WindowsLanguageDiscoverer(self) 1001 self.__client = client 1002 self.__pathAppended = 0 1003 self.__is64Bit = None 1004 1005 # System32 junction point and folder themselves; 1006 # set to None in order to properly initialize them upon discovery on particular destination 1007 self.__ddm_link_system32 = None 1008 self.__system32 = None 1009 1010 Shell.__init__(self, client)
1011
1012 - def isWinOs(self):
1013 '@types: -> bool' 1014 return 1
1015
1016 - def _getOsLanguageDiscoverer(self):
1017 '@types: -> OsLanguageDiscoverer' 1018 return self.__osLanguageDiscoverer
1019
1020 - def getShellCmdSeperator(self):
1021 """ Get the shell command separator character. 1022 This is the character used between commands when more than one command is passed on the same command line. 1023 @types: -> str 1024 @deprecated: Use getCommandSeparator instead 1025 """ 1026 return WinShell.__DEFAULT_COMMAND_SEPARATOR
1027
1028 - def getCodePage(self):
1029 """Get current codepage of remote Windows machine to which the shell client is connected or 437 if codepage was not resolved 1030 @types: -> Integer 1031 @command: chcp 1032 """ 1033 codePage = WinShell.DEFAULT_ENGLISH_CODEPAGE 1034 try: 1035 chcpBuffer = self.execCmd('chcp') 1036 if chcpBuffer and self.getLastCmdReturnCode() == 0: 1037 cpMatch = re.search('(\d{3})',chcpBuffer) 1038 if cpMatch: 1039 codePage = int(cpMatch.group(1)) 1040 except: 1041 logger.warn("Failed to detect codepage, assuming default: 437") 1042 return codePage
1043
1044 - def setCodePage(self, newCodePage = DEFAULT_ENGLISH_CODEPAGE):
1045 """Set new codepage on remote Windows machine to which the shell client is connected 1046 @Types: Integer -> Integer 1047 @command: chcp <codepage> 1048 """ 1049 try: 1050 self.execCmd('chcp %s' % str(newCodePage)) 1051 except: 1052 return 0 1053 else: 1054 return 1
1055
1056 - def getShellStatusVar(self):
1057 '@types: -> str' 1058 return self.__shellStatusVar
1059
1060 - def _getOsType(self):
1061 """ Get the type of the OS running on the target host to which the shell client is connected. 1062 types: -> str 1063 @command: ver 1064 @raise Exception: Failed getting machine OS type. 1065 """ 1066 osBuff = self.execCmd('ver', useCache=1)#@@CMD_PERMISION shell protocol execution 1067 if (self.getLastCmdReturnCode() == self.NO_CMD_RETURN_CODE_ERR_NUMBER and self.__protocolName == 'sshprotocol'): 1068 self.__shellStatusVar = '$?' 1069 osBuff = self.execCmd('ver')#@@CMD_PERMISION shell protocol execution 1070 if (self.getLastCmdReturnCode() != 0): 1071 logger.debug('failed getting os type. command=ver failed with rc=%d' % (self.getLastCmdReturnCode()) + ". Output buffer :" + osBuff) 1072 raise Exception('Failed getting machine OS type.') 1073 else: 1074 match = re.search('(.*)\s*\[', osBuff) 1075 if (match is None): 1076 logger.debug('failed getting os type. unrecognized ver command output') 1077 raise Exception('Failed getting machine OS type.') 1078 else: 1079 self.osType = match.group(1).strip() 1080 return self.osType
1081
1082 - def _getOsVersion(self):
1083 ''' Get OS Version 1084 @types: -> str 1085 @command: ver 1086 @raise Exception: Failed getting os type 1087 ''' 1088 buffer = self.execCmd('ver')#@@CMD_PERMISION shell protocol execution 1089 if (self.getLastCmdReturnCode() != 0): 1090 raise Exception('Failed getting os version. command=ver failed with rc=%d' % (self.getLastCmdReturnCode())) 1091 else: 1092 match = re.search('\[.*\s(.*)\]', buffer) 1093 if match: 1094 return match.group(1) 1095 else: 1096 raise Exception('Failed getting os type. unrecognized ver command output')
1097
1098 - def getWindowsErrorCode(self):
1099 ''' Get the return code for the latest command execution 1100 @types: -> str 1101 @command: echo %ERRORLEVEL% 1102 @deprecated: Will be removed from the public access''' 1103 return self.__client.executeCmd('echo %ERRORLEVEL%')
1104
1105 - def _execute(self, cmd):
1106 '''@types: Command -> Command 1107 @raise Exception: Command execution does not produced output nor return code 1108 ''' 1109 output = self.__client.executeCmd(cmd.line, cmd.executionTimeout, cmd.waitForTimeout) 1110 cmd.outputInBytes = self.__client.getLastCommandOutputBytes() 1111 try: 1112 cmd.returnCode = int(self.getWindowsErrorCode()) 1113 except: 1114 cmd.returnCode = self.NO_CMD_RETURN_CODE_ERR_NUMBER 1115 if output is None: 1116 raise Exception('Executing command: "%s" failed. Command produced no output and no return status (we might be connected to an unstable agent)' % cmd.cmd) 1117 cmd.output = output 1118 return cmd
1119
1120 - def deleteDirectoryViaShellCommand(self, dirPath):
1121 """ Delete directory 1122 @types: str 1123 @comamnd: rmdir "<dirPath>" /s /q 1124 @raise ValueError: Specified path is empty 1125 @raise ValueError: Failed deleting directory 1126 @deprecated: Use methods from file system module instead 1127 """ 1128 if not dirPath: 1129 raise ValueError("dirPath is empty") 1130 self.execCmd("rmdir \"%s\" /s /q" % dirPath) 1131 if self.getLastCmdReturnCode() != 0: 1132 raise ValueError("Failed deleting directory '%s'" % dirPath)
1133
1134 - def createDirectoryViaShellCommand(self, dirPath):
1135 """ Create directory 1136 @types: str 1137 @command: mkdir "<dirPath>" 1138 @raise ValueError: Specified path is empty 1139 @raise ValueError: Failed creating directory 1140 @deprecated: Use methods from file system module instead 1141 """ 1142 if not dirPath: 1143 raise ValueError("dirPath is empty") 1144 self.execCmd("mkdir \"%s\"" % dirPath) 1145 if self.getLastCmdReturnCode() != 0: 1146 raise ValueError("Failed creating directory '%s'" % dirPath)
1147
1148 - def copyFileFromRemoteShare(self, remoteFileName, remoteShareName):
1149 """ Copy file from the remote share resource 1150 @types: str, str -> None 1151 @deprecated: Use methods from file system module instead 1152 """ 1153 return self.__client.getFile(remoteFileName, remoteShareName)
1154
1155 - def deleteRemoteFileFromShare(self, remoteFile, remoteShare):
1156 """ Delete file from the remote share resource 1157 @types: str, str -> None 1158 @deprecated: Use methods from file system module instead 1159 """ 1160 return self.__client.deleteFile(remoteFile, remoteShare)
1161
1162 - def putFile(self, localFilePath, share=DEFAULT_WIN_SHARE):
1163 """ Copy file to the remote share. If file exists it will be rewritten. 1164 @types: str, str -> bool 1165 """ 1166 if self.__client.putFile(localFilePath, share): 1167 remoteFile = self.__composeRemotePath(localFilePath, share) 1168 self.copiedFiles.append(remoteFile) 1169 logger.debug("Create file on destination: ", remoteFile) 1170 return 1
1171
1172 - def deleteFile(self, remoteFile, share=DEFAULT_WIN_SHARE):
1173 """ Delete remote file 1174 @types: str, str -> bool 1175 @deprecated: Use methods from file system module instead 1176 """ 1177 isDeleted = self.__client.deleteFile(remoteFile, share) 1178 if not isDeleted: 1179 self.execCmd('del ' + remoteFile) 1180 isDeleted = not self.getLastCmdReturnCode() 1181 return isDeleted
1182
1183 - def __composeRemotePath(self, localFilePath, share):
1184 '''@types: str, str -> str 1185 @return: String of format "\\<ip address>\<share>\<file name> 1186 ''' 1187 file_ = File(localFilePath) 1188 # since \\localhost doesn't work on Windows 2000 machines, 1189 # try to retrieve the ip address and use it instead: 1190 ipAddress = self.__client.getIpAddress() or '127.0.0.1' 1191 remoteFile = '\\\\' + ipAddress + '\\' + share + '\\' + file_.getName() 1192 return remoteFile
1193
1194 - def copyFileIfNeeded(self, localFile, share=DEFAULT_WIN_SHARE):
1195 """ Copy file to the share if it does not exist there 1196 @types: str, str -> str or None 1197 @command: dir \\ip_address\share\file_name 1198 @deprecated: Use methods from file system module instead 1199 @return: return None if file is not copied 1200 """ 1201 remoteFile = self.__composeRemotePath(localFile, share) 1202 self.execCmd('dir ' + remoteFile)#@@CMD_PERMISION shell protocol execution 1203 1204 if self.getLastCmdReturnCode() != 0: 1205 if self.__client.putFile(localFile, share): 1206 self.copiedFiles.append(remoteFile) 1207 logger.debug("Create file on destination: ", remoteFile) 1208 else: 1209 return None 1210 1211 # Need to figure out why wee need to extend PATH with all these pathes. 1212 if not self.__pathAppended: 1213 try: 1214 interpreter = shell_interpreter.Factory().create(self) 1215 systemRoot = interpreter.getEnvironment().buildVarRepresentation('SystemRoot') 1216 interpreter.getEnvironment().appendPath('PATH', '%s\\system32\\drivers\\etc' % systemRoot, 1217 '%s\\SysWOW64' % systemRoot, 1218 '%s\\system32' % systemRoot) 1219 self.__pathAppended = 1 1220 except: 1221 logger.debugException('Failed to append path using shell_interpreter') 1222 1223 return remoteFile
1224
1225 - def getSystem32DirectoryName(self):
1226 """ Get case sensitive name of Windows %SystemRoot% 1227 @command: dir %SystemRoot% /O:-D | find /I "system32" 1228 @raise ValueError: Failed to find system32 folder inside the %SystemRoot% 1229 @deprecated: Use methods from file system module instead 1230 """ 1231 system32Line = self.execCmd('dir %SystemRoot% /O:-D | find /I "system32"') 1232 system32Name = None 1233 if self.getLastCmdReturnCode() == 0: 1234 match = re.search(r'.*\s+(system32)$', system32Line, re.IGNORECASE) 1235 if match: 1236 system32Name = match.group(1) 1237 1238 if not system32Name: 1239 raise ValueError('Failed to find system32 folder inside the %SystemRoot%') 1240 1241 return system32Name
1242 1285 1305
1306 - def is64BitMachine(self):
1307 """ Checks is the discovered machine OS 64 bit. 1308 32 or 64 bit family detected by checking existence of %SystemRoot%\SysWOW64 folder 1309 @types: -> bool 1310 @command: (@if exist %SystemRoot%\SysWOW64 (echo SysWOW64) ELSE (echo FALSE)) 1311 @deprecated: Will be removed from the public access 1312 """ 1313 if self.__is64Bit is None: 1314 output = self.execCmd('(@if exist %SystemRoot%\SysWOW64 (echo SysWOW64) ELSE (echo FALSE))') 1315 self.__is64Bit = (output.find('SysWOW64') > -1) 1316 return self.__is64Bit
1317
1318 - def fsObjectExists(self, path):
1319 """ Indicates whether object by specified path exists on remote file system 1320 @types: str -> bool 1321 @command: (@if exist %s (echo TRUE) ELSE (echo FALSE)) 1322 @deprecated: Use methods from file system module instead 1323 """ 1324 if path: 1325 output = self.execCmd('(@if exist %s (echo TRUE) ELSE (echo FALSE))' % path) 1326 return output.find('TRUE') > -1
1327
1328 - def safecat(self, path, forceSudo=0):
1329 ''' Get file content by specified path 1330 @types: str, bool -> str 1331 @param forceSudo:if true, always uses sudo, if false first tries to run command without sudo 1332 @command: type 1333 @deprecated: Use file system module for such purposes 1334 @raise ValueError: Illegal type command, contains redirect 1335 @raise Exception: Failed getting contents of file 1336 ''' 1337 # search for redirect character 1338 m = re.search('>', path) 1339 if m is not None: 1340 # constructed cat command contains redirect; 1341 # log warning and do not execute 1342 logger.warn('Illegal type command, contains redirect: [%s]' % path) 1343 raise ValueError('Illegal type command, contains redirect') 1344 1345 path = self.rebuildPath(path) 1346 if (path[0] != '"'): 1347 path = '"' + path + '"' 1348 cmd = 'type %s' % path 1349 self.lastExecutedCommand = cmd 1350 fileContents = self.execCmd(cmd) 1351 if not self.getLastCmdReturnCode(): 1352 return fileContents 1353 logger.warn('Failed getting contents of %s file' % path) 1354 raise Exception('Failed getting contents of file')
1355
1356 - def rebuildPath(self, folder):
1357 ''' Normalize path to one used in OS client connected to 1358 @types: str -> str 1359 @deprecated: Use file system module for such purposes 1360 ''' 1361 return re.sub('/', '\\\\', folder)
1362
1363 - def closeClient(self):
1364 '''Perform cleaning of temporary data on destination system and close the client''' 1365 try: 1366 self.removeSystem32Link() 1367 finally: 1368 Shell.closeClient(self)
1369 1370
1371 -class CygwinShell(WinShell):
1372 'Cygwin Shell base class' 1373
1374 - def isWindowsWithCygwin(self):
1375 '@types: -> bool' 1376 return 1
1377 1378
1379 -class PowerShell(WinShell):
1380 PROTOCOL_TYPE = PowerShellClient.POWERSHELL_PROTOCOL_NAME 1381 __DEFAULT_COMMAND_SEPARATOR = ';' 1382 __INVOKE_COMMAND_SCRIPT_BLOCK = ( 1383 'Invoke-Command -ScriptBlock {%s ;$?} -Session $' 1384 + PowerShellAgent.REMOTE_SESSION_IDENTIFIER) 1385 __INVOKE_COMMAND_LOCAL_SCRIPT = ( 1386 'Invoke-Command -FilePath %s -Session $' 1387 + PowerShellAgent.REMOTE_SESSION_IDENTIFIER 1388 + '; $?') 1389
1390 - def __init__(self, client, protocolName):
1391 '@types: PowerShellClient, str -> None' 1392 self.__client = client 1393 self.__is64Bit = None 1394 self.__outputHandler = PowerShellOutputHandler() 1395 self.__consoleCharsetName = self.__getRemoteConsoleEncoding() 1396 self.__powershellConsoleCharsetName = self.__getPowerShellEncoding() 1397 # list of predefined commands and configured in globalSettings 1398 # configuration file will be executed using CMD interpreter 1399 self.__consoleCommands = ['ver', 'type', 'wmic', 'chcp'] 1400 consoleCommands = (GeneralSettingsConfigFile.getInstance(). 1401 getPropertyTextValue('consoleCommands') 1402 or "") 1403 _isEmptyString = lambda s: s and s.strip() 1404 _normalize = lambda s: s.strip().lower() 1405 consoleCommands = filter(_isEmptyString, consoleCommands.split(',')) 1406 consoleCommands = map(_normalize, consoleCommands) 1407 self.__consoleCommands.extend(consoleCommands) 1408 WinShell.__init__(self, client, protocolName) 1409 self.execCmd('$global:FormatEnumerationLimit = -1', pipeToOutString=0) 1410 self.useCharset('utf8')
1411
1412 - def __getRemoteConsoleEncoding(self):
1413 '''@types: -> str or None 1414 @command: chcp 1415 ''' 1416 cmd = PowerShell.__INVOKE_COMMAND_SCRIPT_BLOCK % 'chcp' 1417 codePage = self.__client.executeCmd(cmd) 1418 if self.__outputHandler.isCommandSucceeded(codePage) == 0: 1419 codePage = self.__outputHandler.cleanOutput(codePage, 0) 1420 codePage = codePage[codePage.rfind(':') + 1:].strip().replace('.', '') 1421 return self.__getEncodingNameByCodePage(codePage)
1422
1423 - def __getPowerShellEncoding(self):
1424 '''@types: ->str or None 1425 @command: [System.Console]::OutputEncoding.WebName 1426 ''' 1427 cmd = '[System.Console]::OutputEncoding.WebName' 1428 cmd = PowerShell.__INVOKE_COMMAND_SCRIPT_BLOCK % cmd 1429 encodingName = self.__client.executeCmd(cmd) 1430 if self.__outputHandler.isCommandSucceeded(encodingName) == 0: 1431 return self.__outputHandler.cleanOutput(encodingName, 0).strip()
1432
1433 - def __getEncodingNameByCodePage(self, codePage):
1434 '''@types: str -> str or None 1435 @command: [System.Text.Encoding]::GetEncoding(<codePage>).WebName 1436 ''' 1437 cmd = '[System.Text.Encoding]::GetEncoding(%s).WebName' % codePage 1438 cmd = PowerShell.__INVOKE_COMMAND_SCRIPT_BLOCK % cmd 1439 encodingName = self.__client.executeCmd(cmd) 1440 if self.__outputHandler.isCommandSucceeded(encodingName) == 0: 1441 return self.__outputHandler.cleanOutput(encodingName, 0).strip()
1442
1443 - def is64BitMachine(self):
1444 """ Checks is the discovered machine OS 64 bit. 32 or 64 bit family 1445 detected by checking existence of %SystemRoot%\SysWOW64 folder 1446 @types: -> bool 1447 @command: Test-Path $env:SystemRoot/SysWOW64 1448 """ 1449 if self.__is64Bit is None: 1450 output = self.execCmd('Test-Path $env:SystemRoot/SysWOW64') 1451 self.__is64Bit = output.lower().count('true') 1452 return self.__is64Bit
1453
1454 - def getShellStatusVar(self):
1455 raise NotImplemented
1456
1457 - def getWindowsErrorCode(self):
1458 raise NotImplemented
1459
1460 - def getCommandSeparator(self):
1461 """Get the shell command separator character. 1462 @types: -> str 1463 """ 1464 return PowerShell.__DEFAULT_COMMAND_SEPARATOR
1465
1466 - def __makePowerShellCompatible(self, cmdline):
1467 ''' If passed command is a windows batch command it should be called in 1468 batch command interpreter 1469 @types: str -> str''' 1470 return "cmd.exe /c '%s'" % cmdline.replace("'", "\\'")
1471
1472 - def __makePowerShellCompatibleWmicQuery(self, cmdline):
1473 '''Strip WMIC redirection part 1474 @types: str -> str 1475 ''' 1476 if re.search('wmic\s', cmdline): 1477 cmdline = re.sub('\<\s* [\w\-\.\,\%\\\/\$]+\s*', '', cmdline) 1478 return cmdline
1479
1480 - def __isConsoleCommand(self, cmdline):
1481 '''Indicates whether passed command line contains command of batch 1482 command interpreter 1483 @types: str -> bool''' 1484 cmd = cmdline.split()[0].lower() 1485 for consoleCmd in self.__consoleCommands: 1486 if re.match('(?<![\w\-])%s$' % consoleCmd, cmd): 1487 logger.debug('A console command') 1488 return 1
1489
1490 - def __pipeToOutString(self, cmd, lineWidth=80):
1491 '@types: str, [int] -> str' 1492 return '%s | Out-String -width %d' % (cmd, lineWidth)
1493
1494 - def execCmd(self, cmdLine, timeout=0, waitForTimeout=0, useSudo=1, 1495 checkErrCode=1, useCache=0, lineWidth=80, pipeToOutString=1):
1496 ''' Execute command in powershell 1497 * If current command is cmdlet: 1498 1. Method passes output object to 'Out-String' cmdlet to get its 1499 string representation. 1500 * If current command is non-cmdlet command(console) 1501 1. Method prefixes current command with 'cmd.exe /c %cmd%' and 1502 no passing to 'Out-String' cmdlet performed. 1503 List of console commands is defined at globalSettings.xml file and 1504 defaults to ('ver', 'type', 'wmic', 'chcp') if 'consoleCommands' 1505 attribute is note set. 1506 1507 @types: str, int, bool, bool, bool, bool, int, bool -> str 1508 @param lineWidth: Attribute of the 'Out-String' cmdlet. 1509 From the msdn: Specifies the number of characters in each line 1510 of output. Any additional characters are truncated, not wrapped 1511 @see: Shell.execCmd for the param meaning and usage 1512 ''' 1513 1514 # PowershellConnector writes its output with utf8 encoding 1515 isConsoleCommand = self.__isConsoleCommand(cmdLine) 1516 if isConsoleCommand: 1517 cmdLine = self.__makePowerShellCompatible(cmdLine) 1518 elif pipeToOutString: 1519 cmdLine = self.__pipeToOutString(cmdLine, lineWidth) 1520 cmdLine = self.__makePowerShellCompatibleWmicQuery(cmdLine) 1521 cmdLine = PowerShell.__INVOKE_COMMAND_SCRIPT_BLOCK % cmdLine 1522 output = Shell.execCmd(self, cmdLine, timeout, waitForTimeout, useSudo, 1523 checkErrCode, useCache) 1524 if isConsoleCommand: 1525 output = self.__fixEncoding(self.__powershellConsoleCharsetName, 1526 self.__consoleCharsetName, output) 1527 return output
1528
1529 - def execLocalScript(self, path, timeout=0, waitForTimeout=0, useSudo=1, 1530 checkErrCode=1, useCache=0):
1531 ''' Execute script on the probe 1532 @types: str, int, bool, bool, bool, bool -> str 1533 ''' 1534 cmd = PowerShell.__INVOKE_COMMAND_LOCAL_SCRIPT % path 1535 return Shell.execCmd(self, cmd, timeout, waitForTimeout, useSudo, 1536 checkErrCode, useCache)
1537
1538 - def execMultilinedCmd(self, cmdLine, timeout=0, waitForTimeout=0, 1539 useSudo=1, checkErrCode=1, useCache=0):
1540 '''Method intended to execute multiline scripts. It's output is not 1541 passed to 'Out-String' cmdlet, so all toString conversion should be 1542 done manually. Powershell remote console encoding used to decode output 1543 Use ';' to separate different commands. 1544 @types: str, int, bool, bool, bool, bool -> str 1545 @see: Shell.execCmd for the param meaning 1546 ''' 1547 cmdLine = "".join(cmdLine.split('\n')) 1548 cmdLine = PowerShell.__INVOKE_COMMAND_SCRIPT_BLOCK % cmdLine 1549 return Shell.execCmd(self, cmdLine, timeout, waitForTimeout, useSudo, 1550 checkErrCode, useCache)
1551
1552 - def _execute(self, cmd):
1553 '''@types: Command -> Command 1554 @raise Exception: Command produced nor output nor return status 1555 ''' 1556 output = self.__client.executeCmd(cmd.line, cmd.executionTimeout, 1557 cmd.waitForTimeout) 1558 cmd.outputInBytes = self.__client.getLastCommandOutputBytes() 1559 if output is None: 1560 raise Exception('Executing command: "%s" failed. ' 1561 'Command produced nor output nor return status ' 1562 '(we might be connected to an unstable agent)' 1563 % cmd.cmd) 1564 output = output.strip() 1565 1566 try: 1567 cmd.returnCode = self.__outputHandler.isCommandSucceeded(output) 1568 except: 1569 cmd.returnCode = self.NO_CMD_RETURN_CODE_ERR_NUMBER 1570 cmd.output = self.__outputHandler.cleanOutput(output, cmd.returnCode) 1571 return cmd
1572
1573 - def __fixEncoding(self, fromEncoding, toEncoding, targetStr):
1574 '''Method is intended to fix string encoding. The root problem comes 1575 from execution of console commands through the powershell. 1576 There are two variables defining encodings: $OutputEncoding 1577 and [Console]::OutputEncoding. The problem is that Exception is raised 1578 when one tries to set remote [Console]::OutputEncoding, so when chcp 1579 output differes from [Console]::OutputEncoding.CodePage value, we need 1580 to fix corrupted string. Fixing is done if passed encodings are 1581 different only 1582 @types: str, str -> str 1583 ''' 1584 if (fromEncoding != toEncoding): 1585 logger.debug('Fixing encoding.. from :%s, to: %s' % (fromEncoding, 1586 toEncoding)) 1587 bytes = String(targetStr).getBytes(fromEncoding) 1588 targetStr = String(bytes, toEncoding) 1589 logger.debug('Fixed string: %s' % targetStr) 1590 return str(targetStr)
1591 1592
1593 -class UnixShell(Shell):
1594 'Unix Shell base class' 1595 ONLY_SUDO_POLICY = 'sudo' 1596 ONLY_SU_POLICY = 'su' 1597 SUDO_SU_POLICY = 'sudo or su' 1598 ERROR_CODE_PREFIX = 'ERROR_CODE:' 1599
1600 - def __init__(self, client):
1601 self.__shellStatusVar = None 1602 self.__shell = None 1603 self.__client = client 1604 self.__sudoPath = None 1605 self.__sudoPathsArray = None 1606 self.__sudoCommandsArray = None 1607 self.__osLanguageDiscoverer = UnixLanguageDiscoverer(self) 1608 self.__sudoConfiguredCommands = None 1609 self.__sudoSuPolicy = None 1610 self.__sudoSplitPattern = "(%s|\s*nice(?:\s*-i\s*\d+)|\s*nice(?:\s*)|\|(?:\s*))" 1611 1612 sudoExcludeCommandsPattern = "\s*export\s+|\s*set\s+|\s*LC_ALL\s*=|\s*LANG\s*=|\s*ORACLE_HOME\s*=|\s*PATH\s*=|\s*DB2NODE\s*=" 1613 self.__sudoExcludeCommandMatcher = re.compile(sudoExcludeCommandsPattern) 1614 1615 Shell.__init__(self, client) 1616 1617 # if the shell supports sudo commands then retrieve sudo paths/commands to use later: 1618 if self.__client.supportsSudo(): 1619 self.__retrieveSudoDetails() 1620 self.setSessionLocale() 1621 # if the osType is 'SunOS' and the shell is '/sbin/sh' - 1622 # limit the maximum command length to 256 (that's the maximum that 1623 # this shell can accept) 1624 if self.__isLimitedCommandLength(): 1625 client.setMaxCommandLength(256) 1626 1627 try: 1628 interpreter = shell_interpreter.Factory().create(self) 1629 interpreter.getEnvironment().appendPath('PATH', '/bin', '/usr/bin') 1630 except: 1631 logger.debugException('Failed to append path using shell_interpreter')
1632
1633 - def isWinOs(self):
1634 '@types: -> bool' 1635 return 0
1636
1637 - def isSudoConfigured(self):
1638 ''' Returns true if there is a valid sudo path at the remote destination 1639 '@types: -> bool' 1640 ''' 1641 return self.__sudoCommandsArray and self.__getSudoPath() and len(self.__getSudoPath()) > 0
1642
1643 - def _getOsLanguageDiscoverer(self):
1644 '@types: -> OsLanguageDiscoverer' 1645 return self.__osLanguageDiscoverer
1646
1647 - def _getOsVersion(self):
1648 ''' Get OS version 1649 @types: -> str 1650 @command: uname -r 1651 @raise Exception: Failed getting OS version 1652 ''' 1653 buffer = self.execCmd('uname -r')#@@CMD_PERMISION shell protocol execution 1654 if (self.getLastCmdReturnCode() == 0): 1655 return buffer 1656 raise Exception('Failed getting OS version. command="uname -r" failed with rc=%d' % (self.getLastCmdReturnCode()))
1657
1658 - def _getOsType(self):
1659 """ Get OS type 1660 @types: -> str 1661 @command: uname 1662 @raise Exception: Failed getting machine OS type. 1663 """ 1664 osBuff = self.execCmd('uname')#@@CMD_PERMISION shell protocol execution 1665 if (self.getLastCmdReturnCode() == 0): 1666 return osBuff.strip() 1667 raise Exception('Failed getting machine OS type.')
1668
1669 - def setSessionLocale(self):
1670 '@deprecated: will be removed from the public access' 1671 locale = self.getAvailableEngLocale() 1672 if locale: 1673 environment = shell_interpreter.Factory().create(self).getEnvironment() 1674 try: 1675 environment.setVariable('LANG', locale) 1676 environment.setVariable('LC_ALL', locale) 1677 except: 1678 logger.debugException('Failed to set new locale') 1679 else: 1680 logger.debug('Locale set to: %s' % locale)
1681
1682 - def getShell(self):
1683 '''Try to retrieve a shell path from Unix machines (like /bin/sh or /sbin/sh) 1684 @types: -> str 1685 @command: echo $SHELL 1686 ''' 1687 if not self.__shell: 1688 logger.debug('determining shell:') 1689 cmd = 'echo $SHELL' 1690 logger.debug("trying: '%s'" % cmd) 1691 resLines = self.__client.executeCmd(cmd) 1692 logger.debug("res: '%s'" % resLines) 1693 if resLines: 1694 resLines = resLines.strip() 1695 logger.debug("response: '%s'" % resLines) 1696 if len(resLines) > 0: 1697 self.__shell = resLines 1698 return self.__shell
1699
1700 - def getShellStatusVar(self):
1701 '@types: -> str' 1702 if not self.__shellStatusVar: 1703 logger.debug('Determining shell environment status variable...') 1704 # in case of csh, 'echo $?' doesn't work, we need to use 1705 # 'echo $status' instead... 1706 shellName = str(self.getShell()) 1707 if (shellName and shellName.endswith('csh')): 1708 self.__shellStatusVar = self.__getCommandExitStatus('$status') 1709 else: 1710 for statusVar in ["$?", "$status", "$STATUS"]: 1711 if (self.__getCommandExitStatus(statusVar) is not None): 1712 self.__shellStatusVar = statusVar 1713 break 1714 return self.__shellStatusVar
1715
1716 - def getAvailableEngLocale(self):
1717 '''@types: -> str or None 1718 @command: locale -a | grep -E "en_US.*|^C|POSIX" 1719 @command: locale -a | /usr/xpg4/bin/grep -E "en_US.*|^C|POSIX" 1720 ''' 1721 buff = self.execAlternateCmds('locale -a | grep -E "en_US.*|^C|POSIX"', 'locale -a | /usr/xpg4/bin/grep -E "en_US.*|^C|POSIX"') 1722 if buff: 1723 match = re.search(r'en_US\S+', buff) 1724 if match: 1725 return match.group(0).strip() 1726 match = re.search(r'(?<![\w])C\s+', buff) 1727 if match: 1728 return match.group(0).strip() 1729 match = re.search(r'POSIX\S+', buff) 1730 if match: 1731 return match.group(0).strip() 1732 return None
1733
1734 - def __getCommandExitStatus(self, statusVar):
1735 '''@types: str -> str or None 1736 @command: echo <env variable> 1737 ''' 1738 cmd = 'echo %s' % statusVar 1739 logger.debug('trying: \'%s\'...' % cmd) 1740 resLines = self.__client.executeCmd(cmd) 1741 logger.debug('res: \'%s\'...' % resLines) 1742 if (resLines is not None): 1743 resLines = resLines.strip() 1744 logger.debug('response: \'%s\'' % resLines) 1745 resLinesCount = len(resLines) 1746 if ((resLinesCount > 0) and (resLines[resLinesCount - 1].isdigit())): 1747 logger.debug('found shell environment status variable=\'%s\'' % statusVar) 1748 return statusVar 1749 return None
1750
1751 - def __isLimitedCommandLength(self):
1752 ''' Specific for Sun OS where shell 'sh' has command length limitation 1753 @types: -> bool 1754 ''' 1755 #TODO: SunShell ? 1756 return self.getOsType() == 'SunOS' and self.getShell() == '/sbin/sh'
1757
1758 - def __executeCommand(self, command):
1759 '@types: Command -> Command' 1760 cmd = command.line 1761 separator = self.getShellCmdSeperator() 1762 1763 cmdWithRc = '%s %s echo %s%s' % (cmd, separator, self.ERROR_CODE_PREFIX, self.getShellStatusVar()) 1764 buff = self.__client.executeCmd(cmdWithRc, command.executionTimeout, command.waitForTimeout) 1765 command.outputInBytes = self.__client.getLastCommandOutputBytes() 1766 if buff is not None: 1767 try: 1768 output, returnCode = _extractCommandOutputAndReturnCode(buff) 1769 except ValueError: 1770 command.returnCode = Shell.NO_CMD_RETURN_CODE_ERR_NUMBER 1771 logger.warn("Execution command: \'%s\' failed (we might be connected to an unstable agent)" % cmd) 1772 raise Exception("Command produced no return status") 1773 else: 1774 command.returnCode = returnCode 1775 command.output = output 1776 else: 1777 command.returnCode = self.NO_CMD_RETURN_CODE_ERR_NUMBER 1778 logger.warn("Executing command: \'%s\' failed (we might be connected to an unstable agent)" % cmd) 1779 raise Exception("Command produced no output and no return status") 1780 return command
1781
1782 - def __executeCommandWithSu(self, command):
1783 self.__client.executeSuCommand() 1784 if not self.__client.isInSuMode(): 1785 raise Exception('Failed to run command in su mode.') 1786 command = self.__executeCommand(command) 1787 self.__client.exitFromSu() 1788 return command
1789
1790 - def __canUseSudo(self, originalCommand):
1791 '@types: str, str -> bool' 1792 return (self.__sudoCommandsArray 1793 and self.__shouldRunAsSuperUser(originalCommand, 1))
1794
1795 - def __shouldRunAsSuperUser(self, originalCommand, checkDestinationConfiguration=None):
1796 separator = self.getShellCmdSeperator() 1797 splitPattern = self.__sudoSplitPattern % re.escape(separator) 1798 commandElements = re.split(splitPattern, originalCommand) 1799 for i in range(0, len(commandElements)): 1800 command = commandElements[i] 1801 if not command or re.match(splitPattern, command): 1802 #we do not want to process split elements since they aren't commands 1803 continue 1804 isDestinationConfigured = 1 1805 if checkDestinationConfiguration: 1806 isDestinationConfigured = self.__isCommandConfiguredOnDestination(command) 1807 1808 if isDestinationConfigured and self.__shouldCommandElemRunAsSuperUser(command): 1809 return 1 1810 1811 return None
1812
1813 - def __isCommandConfiguredOnDestination(self, command):
1814 sudoConfiguredCommands = self.__getSudoConfiguredCommands() 1815 if sudoConfiguredCommands: 1816 for configuredCommand in sudoConfiguredCommands: 1817 if re.match(configuredCommand, command.strip()): 1818 logger.debug('Command %s is configured to run with sudo on destination.' % command) 1819 return 1
1820
1822 if self.__sudoCommandsArray: 1823 isConfiguredInCreds = 0 1824 for sudoCommandPattern in self.__sudoCommandsArray: 1825 if sudoCommandPattern and sudoCommandPattern.strip() == '.*': 1826 isConfiguredInCreds = 1 1827 1828 if isConfiguredInCreds: 1829 sudoConfiguredCommands = self.__getSudoConfiguredCommands() 1830 for sudoCommandPattern in sudoConfiguredCommands: 1831 if sudoCommandPattern and sudoCommandPattern.strip() == '.*': 1832 return 1
1833
1834 - def __shouldCommandElemRunAsSuperUser(self, command):
1835 if self.__sudoCommandsArray: 1836 for sudoCommandPattern in self.__sudoCommandsArray: 1837 if re.match(sudoCommandPattern.lstrip(), command.strip()): 1838 logger.debug("Command %s is matched by privileged command pattern %s" % (command, sudoCommandPattern)) 1839 return 1
1840
1841 - def __canUseSu(self, originalCommand):
1842 try: 1843 return self.__client.isSuConfigured() and self.__shouldRunAsSuperUser(originalCommand) 1844 except: 1845 logger.error('Method isSuConfigured() is missing in client. Wrong content.jar used. Please update.')
1846
1847 - def __getProtocolManager(self):
1848 from com.hp.ucmdb.discovery.library.credentials.dictionary import ProtocolManager 1849 return ProtocolManager
1850
1851 - def __getSudoSuPolicy(self):
1852 if self.__sudoSuPolicy is None: 1853 try: 1854 protocol = self.__getProtocolManager().getProtocolById(self.getCredentialId()) 1855 protocolName = self.getClientType() 1856 sudoPolicy = None 1857 if protocolName in ['ssh', 'telnet']: 1858 sudoPolicy = protocol.getProtocolAttribute('%sprotocol_sudo_su_policy' % protocolName) 1859 self.__sudoSuPolicy = sudoPolicy or UnixShell.ONLY_SUDO_POLICY 1860 except: 1861 self.__sudoSuPolicy = UnixShell.ONLY_SUDO_POLICY 1862 return self.__sudoSuPolicy
1863
1864 - def _execute(self, command):
1865 '@types: Command -> Command' 1866 #We always expect new line after command execution but there are systems which do not append new line after command output buffer. 1867 #Since we append error code after output buffer we need to introduce separator to separate error code from the command output buffer. 1868 #For this purpose we use ERROR_CODE_PREFIX 1869 sudoSuPolicy = self.__getSudoSuPolicy() 1870 cmd = command.line 1871 if command.useSudo: 1872 if self.__client.supportsSudo() and sudoSuPolicy != UnixShell.ONLY_SU_POLICY and self.__canUseSudo(cmd): 1873 command.line = self.__prepareCmdForSudo(cmd, command.preserveSudoContext) 1874 return self.__executeCommand(command) 1875 elif sudoSuPolicy != UnixShell.ONLY_SUDO_POLICY and self.__canUseSu(cmd): 1876 return self.__executeCommandWithSu(command) 1877 1878 return self.__executeCommand(command)
1879
1880 - def __retrieveSudoDetails(self):
1881 '''Get information about sudo paths and sudo enabled commands''' 1882 sudoPaths = self.__client.getSudoPaths() 1883 if sudoPaths: 1884 self.__sudoPathsArray = string.split(sudoPaths, ',') 1885 sudoCommands = self.__client.getSudoCommands() 1886 if sudoCommands: 1887 splitCommands = string.split(sudoCommands, ',') 1888 if '*' in splitCommands: 1889 self.__sudoCommandsArray = ['.*'] 1890 else: 1891 self.__sudoCommandsArray = splitCommands
1892
1893 - def __prepareCmdForSudo(self, cmd, preserveSudoContext = 0):
1894 '@types: str -> str' 1895 # in case we don't have sudo details - return the command itself 1896 if not self.__sudoPathsArray: 1897 return cmd 1898 # if the command already contains sudo - return the command 1899 if re.match('\S*sudo\s', cmd): 1900 return cmd 1901 1902 sudoPath = self.__getSudoPath() 1903 1904 if sudoPath and len(sudoPath) > 0: 1905 return self.__getSudoWithCmd(sudoPath, cmd, preserveSudoContext) 1906 else: 1907 return cmd
1908
1909 - def __parseSudoConfiguredCommands(self, output):
1910 '@types: str -> list(str)' 1911 sudoConfiguredCommandsList = [] 1912 for line in re.split('[\r\n\,]+', output): 1913 if line.find('following commands on') != -1: 1914 continue 1915 match = re.match('\s+.*?\s+(ALL)|\s+(?!\/).*?\s+/([\w\ \-\/\.\*]+)|\s+/([\w\ \-\/\.\*]+)', line) 1916 if match: 1917 command = match.group(1) or match.group(2) or match.group(3) 1918 command = command.replace('*', '.*') 1919 if command.find(',') == -1 and command != 'ALL': 1920 sudoConfiguredCommandsList.append('/%s' % command) 1921 match = re.match('\S+/(.*)', command) 1922 if match and match.group(1).strip() and match.group(1).strip() != command: 1923 sudoConfiguredCommandsList.append(match.group(1).strip()) 1924 elif command == 'ALL': 1925 sudoConfiguredCommandsList = ['.*'] 1926 break 1927 return sudoConfiguredCommandsList
1928
1930 '''@types: -> list(str) 1931 @command: sudo -l 1932 If "sudo -l" command fails to execute for any reason returning '.*' pattern which would accept all commands. 1933 ''' 1934 if self.__sudoConfiguredCommands is None: 1935 try: 1936 sudoPath = self.__getSudoPath() 1937 output = self.execCmd('%s %s' % (sudoPath, '-l'), useSudo=0) 1938 if not output or self.getLastCmdReturnCode() != 0: 1939 raise ValueError('Failed to run sudo -l') 1940 self.__sudoConfiguredCommands = self.__parseSudoConfiguredCommands(output) 1941 except: 1942 logger.warn('Failed to list SUDO configured commands. Assuming all commands are accepted.') 1943 self.__sudoConfiguredCommands = ['.*'] 1944 return self.__sudoConfiguredCommands
1945
1946 - def __getSudoWithCmd(self, sudoPath, originalCommand, preserveSudoContext = 0):
1947 '@types: str, str -> str' 1948 if not self.__sudoCommandsArray: 1949 logger.debug('No sudo commands specified.') 1950 return originalCommand 1951 else: 1952 sudoPathWithParams = sudoPath 1953 if preserveSudoContext: 1954 sudoPathWithParams = '%s %s' % (sudoPath, '-E') 1955 separator = self.getShellCmdSeperator() 1956 1957 if self.__shouldAllCommandsRunAsSuperUser(): 1958 if self.__sudoExcludeCommandMatcher.match(originalCommand): 1959 logger.debug('Command is in excluded for sudo commands list. Sudo will not be added.') 1960 return originalCommand 1961 logger.debug('Both credentials and destination are configured to run all commands as Super User.') 1962 return '%s %s -c "%s"' % (sudoPathWithParams, self.getShell(), re.sub(r'(?<!\\)"', r'\\"', originalCommand)) 1963 1964 splitPattern = self.__sudoSplitPattern % re.escape(separator) 1965 commandElements = re.split(splitPattern, originalCommand) 1966 for i in range(0, len(commandElements)): 1967 command = commandElements[i] 1968 if not command or re.match(splitPattern, command): 1969 #we do not want to process split elements since they aren't commands 1970 continue 1971 if self.__shouldCommandElemRunAsSuperUser(command): 1972 if self.__isCommandConfiguredOnDestination(command): 1973 logger.debug("Command %s will be prefixed with %s" % (command, sudoPathWithParams)) 1974 command = "%s %s" % (sudoPathWithParams, command) 1975 else: 1976 logger.warn('Command %s matches the UCMDB privileged command patterns but is not in the sudo list on the destination.' % command) 1977 commandElements[i] = command 1978 1979 return ''.join(commandElements)
1980
1981 - def __getSudoPath(self):
1982 '@types: -> str' 1983 if self.__sudoPath is not None: 1984 return self.__sudoPath 1985 else: 1986 if (not self.__sudoPathsArray or not(len(self.__sudoPathsArray) > 0)): 1987 self.__sudoPath = '' 1988 else: 1989 for path in self.__sudoPathsArray: 1990 cmdWithRc = '%s%secho %s' % (path + ' -V', self.getShellCmdSeperator(), self.getShellStatusVar()) 1991 logger.debug('__getSudoPath: checking "%s" command' % cmdWithRc) 1992 buff = self.__client.executeCmd(cmdWithRc, 0, 0) 1993 if (buff is None): 1994 logger.debug('__getSudoPath: execution of %s failed - command produced no output and no return status' % cmdWithRc) 1995 else: 1996 keepends = 1 1997 resLines = buff.strip().splitlines(keepends) 1998 lastLineNum = len(resLines) - 1 1999 # separate the last line in order to isolate the return status 2000 if (lastLineNum < 0): 2001 logger.debug('__getSudoPath: no output was received for %s - proceeding to check next path' % cmdWithRc) 2002 continue 2003 lastLine = resLines[lastLineNum].strip() 2004 try: 2005 lastCmdReturnCode = int(lastLine.strip()) 2006 #noinspection PySimplifyBooleanCheck 2007 if lastCmdReturnCode == 0: 2008 logger.debug("__getSudoPath: execution of '%s' succeeded - setting it as sudo path" % cmdWithRc) 2009 self.__sudoPath = path 2010 return self.__sudoPath 2011 except: 2012 logger.debug('__getSudoPath: error parsing return code for command: %s=> returned output: %s' % (cmdWithRc, lastLine)) 2013 continue 2014 logger.debug('__getSudoPath: none of the supplied sudo paths is valid - proceeding without sudo support') 2015 self.__sudoPath = '' 2016 return self.__sudoPath
2017
2018 - def fsObjectExists(self, path):
2019 '''Indicates whether object by specified path exists on remote file system 2020 @types: str -> bool 2021 @command: if [ -d \"%s\" ]; then echo true ; else echo false; fi 2022 @command: if [ -f \"%s\" ]; then echo true ; else echo false; fi 2023 @deprecated: Use methods from file system module instead''' 2024 if path: 2025 output = self.execCmd("if [ -d \"%s\" ]; then echo true ; else echo false; fi" % path) 2026 if output.find('true') > -1: 2027 return 1 2028 output = self.execCmd("if [ -f \"%s\" ]; then echo true ; else echo false; fi" % path) 2029 if output.find('true') > -1: 2030 return 1
2031
2032 - def safecat(self, path, forceSudo=0):
2033 ''' Get content of file by specified path 2034 @types: str, bool -> str 2035 @param forceSudo:if true, always uses sudo, if false first tries to run command without sudo 2036 @command: cat <path> 2037 @raise ValueError: Illegal cat command, contains redirect 2038 @raise EnvironmentError: sudo for cat is not available on server 2039 @raise Exception: Failed getting contents of file 2040 ''' 2041 # search for redirect character 2042 if re.search('>', path): 2043 # constructed cat command contains redirect; log error and do not execute 2044 logger.error('Illegal cat command, contains redirect: [%s]' % path) 2045 raise ValueError('Illegal cat command, contains redirect') 2046 2047 path = self.rebuildPath(path) 2048 #If we are not forced to use sudo - first try regular cat - in case 2049 #it doesn't work - try sudo cat... 2050 cmd = 'cat %s' % path 2051 self.lastExecutedCommand = cmd 2052 if not forceSudo: 2053 fileContents = self.execCmd(cmd, useSudo=0)#@@CMD_PERMISION shell protocol execution 2054 if not self.getLastCmdReturnCode(): 2055 return fileContents 2056 fileContents = self.execCmd(cmd)#@@CMD_PERMISION shell protocol execution 2057 if not self.getLastCmdReturnCode(): 2058 return fileContents 2059 else: 2060 m = re.search('you do not have access', fileContents.lower()) 2061 if m is not None: 2062 raise EnvironmentError('sudo for cat is not implemented on server') 2063 elif re.search('not found', fileContents.lower()): 2064 raise EnvironmentError('sudo is not available on server') 2065 else: 2066 logger.warn('Failed getting contents of %s file' % path) 2067 raise Exception('Failed getting contents of file')
2068
2069 - def rebuildPath(self, folder):
2070 ''' Normalize path to one used in OS client connected to 2071 @types: str -> str 2072 @deprecated: Use file system module for such purposes 2073 ''' 2074 return re.sub('\\\\', '/', folder)
2075 2076
2077 -def _extractCommandOutputAndReturnCode(buffer):
2078 r''' Extract output and return code information by splitting output into 2079 two parts by ERROR_CODE_PREFIX substring, used in unix shell 2080 @types: str -> (str, int) 2081 @raise ValueError: No return code information found 2082 ''' 2083 errCodeIndex = buffer.rfind(UnixShell.ERROR_CODE_PREFIX) 2084 if errCodeIndex != -1: 2085 output, errorCode = buffer, Shell.NO_CMD_RETURN_CODE_ERR_NUMBER 2086 output = buffer[:errCodeIndex] 2087 lastLine = buffer[errCodeIndex:].strip() 2088 returnCodeStr = lastLine[len(UnixShell.ERROR_CODE_PREFIX):] 2089 try: 2090 errorCode = int(returnCodeStr) 2091 except: 2092 logger.debug("Failed to process errorcode value: %s" % returnCodeStr) 2093 return (output, errorCode) 2094 raise ValueError("No return code information found")
2095 2096
2097 -class MacShell(UnixShell):
2098 - def getAvailableEngLocale(self):
2099 return "C"
2100
2101 - def setSessionLocale(self):
2102 pass
2103 2104
2105 -class VIOShell(UnixShell):
2106 '''Shell for Virtual I/O Server 2107 ( part of the IBM eServer p5 Advanced Power Virtualization hardware feature)''' 2108
2109 - def __prepareCmd(self, cmdLine):
2110 ''' Prepare passed command line to run as padmin user 2111 @types: str -> str''' 2112 return 'ioscli %s' % cmdLine
2113
2114 - def execIoscliCmd(self, cmdLine, *args, **kwargs):
2115 ''' Run command as padmin user, using command ioscli 2116 @types: str -> str''' 2117 return self.execCmd(self.__prepareCmd(cmdLine), *args, **kwargs)
2118
2119 - def _getOsType(self):
2120 """ Get OS type 2121 @types: -> str 2122 @command: uname 2123 @raise Exception: Failed getting machine OS type. 2124 """ 2125 2126 osBuff = self.execIoscliCmd('uname')#@@CMD_PERMISION shell protocol execution 2127 if (self.getLastCmdReturnCode() == 0): 2128 return osBuff.strip() 2129 raise Exception('Failed getting machine OS type.')
2130
2131 - def _getOsVersion(self):
2132 ''' Get OS version 2133 @types: -> str 2134 @command: uname -r 2135 @raise Exception: Failed getting OS version. 2136 ''' 2137 buffer = self.execIoscliCmd('uname -r')#@@CMD_PERMISION shell protocol execution 2138 if (self.getLastCmdReturnCode() == 0): 2139 return buffer 2140 raise Exception('Failed getting os version. command="ioscli uname -r" failed with rc=%d' % (self.getLastCmdReturnCode()))
2141 2142
2143 -def getLanguageBundle(baseName, language, framework):
2144 '@types: str, str, Framework -> bundle' 2145 languageName = language.bundlePostfix #language.locale.toString() 2146 return framework.getEnvironmentInformation().getBundle(baseName, languageName)
2147 2148
2149 -def readLocalFile(localFilePath, encoding="utf-8"):
2150 ''' Read file on the probe machine by the specified path 2151 @types: str, str -> str or None 2152 @deprecated: Use methods from file system module instead 2153 @raise ValueError: specified localFilePath is empty 2154 ''' 2155 if not localFilePath: 2156 raise ValueError("localFilePath is empty") 2157 fileObject = None 2158 try: 2159 fileObject = codecs.open(localFilePath, "r", encoding) 2160 return fileObject.read() 2161 finally: 2162 if fileObject is not None: 2163 try: 2164 fileObject.close() 2165 except: 2166 pass
2167 2168
2169 -def deleteLocalFile(localFilePath):
2170 ''' Delete file on the probe machine by the specified path 2171 @types: str -> bool 2172 @deprecated: Use methods from file system module instead 2173 @raise ValueError: specified localFilePath is empty 2174 ''' 2175 if not localFilePath: 2176 raise ValueError("localFilePath is empty") 2177 try: 2178 file_ = File(localFilePath) 2179 return file_.delete() 2180 except: 2181 logger.debugException("Failed deleting file '%s'\n" % localFilePath)
2182