Newer
Older
tsshbatch / x.py
  1. #!/usr/bin/env python
  2. # tsshbatch.py - Non-Interactive ssh Connection
  3. # Copyright (c) 2011-2014 TundraWare Inc.
  4. # Permission Hereby Granted For Unrestricted Personal Or Commercial Use
  5. # See "tsshbatch-license.txt" For Licensing Details
  6. #
  7. # For Updates See: http://www.tundraware.com/Software/tsshbatch
  8.  
  9. # A tip of the hat for some of the ideas in the program goes to:
  10. #
  11. # http://jessenoller.com/2009/02/05/ssh-programming-with-paramiko-completely-different/
  12.  
  13. #####
  14. # Program Housekeeping
  15. #####
  16.  
  17. PROGNAME = "tsshbatch.py"
  18. BASENAME = PROGNAME.split(".py")[0]
  19. PROGENV = BASENAME.upper()
  20. CMDINCL = PROGENV + "CMDS"
  21. HOSTINCL = PROGENV + "HOSTS"
  22.  
  23. CVSID = "$Id: x.py,v 1.2 2014/07/25 21:28:45 tundra Exp $"
  24. VERSION = CVSID.split()[2]
  25. CPRT = "(c)"
  26. DATE = "2011-2014"
  27. OWNER = "TundraWare Inc."
  28. RIGHTS = "All Rights Reserved."
  29. COPYRIGHT = "Copyright %s %s, %s %s" % (CPRT, DATE, OWNER, RIGHTS)
  30.  
  31. PROGVER = PROGNAME + " " + VERSION + (" - %s" % COPYRIGHT)
  32. HOMEPAGE = "http://www.tundraware.com/Software/%s\n" % BASENAME
  33.  
  34.  
  35. #####
  36. # Suppress Deprecation Warnings
  37. # Required in some older environments where paramiko version
  38. # is behind the python libs version.
  39. #####
  40.  
  41. import warnings
  42. warnings.filterwarnings("ignore", "", DeprecationWarning)
  43.  
  44.  
  45. #####
  46. # Imports
  47. #####
  48.  
  49. import getopt
  50. import getpass
  51. import os
  52. import paramiko
  53. import shlex
  54. import socket
  55. import sys
  56.  
  57.  
  58. #####
  59. # Constants And Literals
  60. #####
  61.  
  62.  
  63. ABORTING = 'Aborting ...'
  64. COMMENT = '#'
  65. COMMANDS = 'Commands'
  66. CONSUCCESS = 'SUCCESS: Connection Established'
  67. GETFILES = 'Files To GET'
  68. HOSTSEP = ':'
  69. HOSTNOISE = '[%s]'
  70. HOSTLIST = 'Hosts'
  71. INDENTWIDTH = 8
  72. OPTIONSLIST = 'EKG:H:NP:ST:aef:hkn:p:tvxy'
  73. PADWIDTH = 12
  74. PATHDELIM = ':'
  75. PATHSEP = os.sep
  76. PUTFILES = 'Files To PUT'
  77. SEPARATOR = ' ---> '
  78. STDIN = '-'
  79. SUDO = 'sudo'
  80. SUDOPROMPT = 'READINGSUDOPW'
  81. SUDOARGS = '-S -k -p %s' % SUDOPROMPT
  82. SUDOPWHINT = '(Default: login password): '
  83. SYMTABLE = 'Symbol Table'
  84. TESTRUN = 'Test Run For'
  85. TRAILER = ': '
  86. USERVAR = 'USER'
  87.  
  88. USAGE = \
  89. PROGVER + "\n" +\
  90. HOMEPAGE + "\n\n" +\
  91. "Usage: tsshbatch.py [-EKNSTaehkvxy -G 'file dest' -P 'file dest' -f cmdfile -n name -p pw ] -H 'host ..' | hostlistfile [command arg ... ]\n" +\
  92. " where,\n" +\
  93. "\n" +\
  94. " -E Write error output to stdout instead of stderr\n" +\
  95. " -K Force password prompting - Overrides previous -k\n" +\
  96. " -G 'file dest' GET file on host and write local dest directory\n" +\
  97. " -H '...' List of targeted hosts passed as a single argument\n" +\
  98. " -N Force prompting for username\n" +\
  99. " -P 'file dest' PUT local file to host dest directory\n" +\
  100. " -S Force prompting for sudo password\n" +\
  101. " -T seconds Timeout for ssh connection attempts\n" +\
  102. " -a Don't abort program after failed file transfers." +\
  103. " -e Don't report remote host stderr output\n" +\
  104. " -f cmdfile Read commands from file\n" +\
  105. " -h Display help\n" +\
  106. " -k Use key exchange-based authentication\n" +\
  107. " -n name Specify login name\n" +\
  108. " -p pw Specify login password\n" +\
  109. " -t Run in test mode, don't actually execute commands\n" +\
  110. " -v Display extended program version information\n" +\
  111. " -x Turn off test mode (if on) and execute requests\n" +\
  112. " -y Turn on 'noisy' reporting for additional detail\n"
  113.  
  114.  
  115. #####
  116. # Directives & Related Support
  117. #####
  118.  
  119. ASSIGN = '='
  120. DEFINE = '.define'
  121. INCLUDE = '.include'
  122.  
  123.  
  124. #####
  125. # Hostname substitution support
  126. #####
  127.  
  128.  
  129. HOSTNAME = '<HOSTNAME>'
  130. HOSTSHORT = '<HOSTSHORT>'
  131. HostName = ""
  132. HostShort = ""
  133.  
  134. SymbolTable = {}
  135.  
  136.  
  137. #####
  138. # Error Messages
  139. #####
  140.  
  141. eBADARG = "Invalid command line: %s!"
  142. eBADFILE = "Cannot open '%s'!"
  143. eBADSUDO = "sudo Failed (Check Password Or Command!) sudo Error Report: %s"
  144. eBADTXRQ = "Bad Transfer Request: %s Must Have Exactly 1 Source And 1 Destination!"
  145. eBADDEFINE = "Bad Symbol Definition: %s"
  146. eBADTIMEOUT = "Timeout Value Must Be an Integer!"
  147. eCMDFAILURE = "Failed To Run Command(s): %s"
  148. eFXERROR = "File Transfer Error: %s"
  149. eINCLUDECYCLE = "Circular Include At: %s"
  150. eNOCONNECT = "Cannot Connect: %s"
  151. eNOHOSTS = "No Hosts Specified!"
  152. eNOLOGIN = "Cannot Login! (Login/Password Bad?)"
  153.  
  154.  
  155. #####
  156. # Informational Messages
  157. #####
  158.  
  159. iTXFILE = "Writing %s To %s ..."
  160.  
  161.  
  162. #####
  163. # Prompts
  164. #####
  165.  
  166. pPASS = "Password: "
  167. pSUDO = "%s Password: " % SUDO
  168. pUSER = "Username (%s): "
  169.  
  170.  
  171. #####
  172. # Options That Can Be Overriden By User
  173. ####
  174.  
  175. ABORTONFXERROR = True # Abort after a file transfer error
  176. GETSUDOPW = False # Prompt for sudo password
  177. Hosts = [] # List of hosts to target
  178. KEYEXCHANGE = False # Do key exchange-based auth?
  179. NOISY = False # Print output with extra detail
  180. PROMPTUSERNAME = False # Don't use $USER, prompt for username
  181. PWORD = "" # Password
  182. REDIRSTDERR = False # Redirect stderr to stdout
  183. REPORTERR = True # Report stderr output from remote host
  184. TESTMODE = True # Run program in test mode, don't actually execute commands
  185. TIMEOUT = 15 # Connection attempt timeout (sec)
  186. UNAME = "" # Login name
  187.  
  188.  
  189. #####
  190. # Global Data Structures
  191. #####
  192.  
  193. Commands = []
  194. FileIncludeStack = []
  195. Get_Transfer_List = {}
  196. Put_Transfer_List = {}
  197.  
  198.  
  199. #####
  200. # Functions
  201. #####
  202.  
  203. # Gets rid of comments and strips leading/trailing whitespace
  204.  
  205. def ConditionLine(line):
  206. return line.split(COMMENT)[0].strip()
  207.  
  208. # End of 'ConditionLine()'
  209.  
  210.  
  211. #####
  212. # Print Message(s) To stderr
  213. #####
  214.  
  215. def PrintStderr(msg, TERMINATOR="\n"):
  216.  
  217. # If we've been told to redirect to stdout, do so instead
  218.  
  219. if REDIRSTDERR:
  220. PrintStdout(msg, TERMINATOR)
  221.  
  222. else:
  223.  
  224. sys.stderr.write(msg + TERMINATOR)
  225. sys.stderr.flush()
  226.  
  227. # End of 'PrintStderr()'
  228.  
  229.  
  230. #####
  231. # Print Message(s) To stdout
  232. #####
  233.  
  234. def PrintStdout(msg, TERMINATOR="\n"):
  235. sys.stdout.write(msg + TERMINATOR)
  236. sys.stdout.flush()
  237.  
  238. # End of 'PrintStdout()'
  239.  
  240.  
  241. #####
  242. # Display An Error Message And Exit
  243. #####
  244.  
  245. def ErrorExit(msg):
  246.  
  247. if msg:
  248. PrintStderr(msg)
  249.  
  250. os._exit(1)
  251.  
  252. # End Of 'ErrorExit()'
  253.  
  254.  
  255. #####
  256. # Transfer Files To A Host
  257. #####
  258.  
  259. def HostFileTransfer(host, user, pw, filelist, GET=False):
  260.  
  261. try:
  262. ssh = paramiko.SSHClient()
  263.  
  264. # Connect and run the command, reporting results as we go
  265. ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  266.  
  267. if KEYEXCHANGE:
  268. ssh.connect(host, timeout=TIMEOUT)
  269. else:
  270. ssh.connect(host, username=user, password=pw, timeout=TIMEOUT)
  271.  
  272. sftp = ssh.open_sftp()
  273.  
  274. for src in filelist:
  275.  
  276. # Resolve references to hostname in source file specification
  277.  
  278. srcfile = src.replace(HOSTNAME, HostName)
  279. srcfile = src.replace(HOSTSHORT, HostShort)
  280.  
  281. for destdir in filelist[src]:
  282. # Resolve references to hostname in destination directory specification
  283.  
  284. destdir = destdir.replace(HOSTNAME, HostName)
  285. destdir = destdir.replace(HOSTSHORT, HostShort)
  286.  
  287. # Make sure we have a trailing path separator
  288. destination = destdir
  289. if destination[-1] != PATHSEP:
  290. destination += PATHSEP
  291.  
  292. if GET:
  293. destination += host + HOSTSEP + os.path.basename(srcfile)
  294. PrintStdout(iTXFILE % (host + ":" + srcfile, destination))
  295. sftp.get(srcfile, destination)
  296.  
  297. else:
  298. destination += os.path.basename(srcfile)
  299. PrintStdout(iTXFILE % (srcfile, host + ":" + destination))
  300. sftp.put(srcfile, destination)
  301.  
  302. sftp.close()
  303. ssh.close()
  304.  
  305. except:
  306.  
  307. PrintReport([host, eFXERROR % str(sys.exc_info()[1])], HANDLER=PrintStderr)
  308.  
  309. try:
  310. sftp.close()
  311. ssh.close()
  312.  
  313. except:
  314. pass
  315.  
  316. # Do we continue or not?
  317. if ABORTONFXERROR:
  318. ErrorExit("")
  319.  
  320. # End of 'HostFileTransfer()'
  321.  
  322. def HostCommands(host, user, pw, sudopw, commands):
  323.  
  324. ssh = paramiko.SSHClient()
  325.  
  326. # Connect and run the command, reporting results as we go
  327. try:
  328. ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  329.  
  330. if KEYEXCHANGE:
  331. ssh.connect(host, timeout=TIMEOUT)
  332.  
  333. else:
  334. ssh.connect(host, username=user, password=pw, timeout=TIMEOUT)
  335.  
  336. # Set up a pty
  337. chan = ssh.invoke_shell()
  338.  
  339. PrintReport([host, CONSUCCESS])
  340.  
  341. # Run all requested commands
  342.  
  343. for command in commands:
  344.  
  345. # Resolve references to hostname within the command string
  346.  
  347. command = command.replace(HOSTNAME, HostName)
  348. command = command.replace(HOSTSHORT, HostShort)
  349.  
  350. # It's possible to get blank lines from stdin.
  351. # Ignore them.
  352.  
  353. if not command:
  354. continue
  355.  
  356. # If this is a sudo run, we have to use ptys to work around requiretty settings
  357.  
  358. if command.startswith(SUDO + " "):
  359.  
  360. command = command.replace(SUDO, "%s %s" % (SUDO, SUDOARGS), 1)
  361.  
  362. # Invoke The Command
  363.  
  364. chan.send(command + "&& exit\n")
  365.  
  366. # Provide password
  367.  
  368. rxbuffer = ''
  369. while not rxbuffer.endswith(SUDOPROMPT):
  370. rxbuffer += chan.recv(9999)
  371.  
  372. chan.send("%s\n" % sudopw)
  373.  
  374. results = ''
  375. READMORE = True
  376. while READMORE:
  377.  
  378. newresults = chan.recv(9999)
  379. if newresults:
  380. results += newresults
  381.  
  382. else:
  383. READMORE = False
  384.  
  385. # Convert the result which is one big string into a list of strings on newline boundaries
  386.  
  387. results = results.replace("\r", "")
  388. listresults = results.split("logout")[0].split("\n")
  389. listresults = [e + "\n" for e in listresults]
  390.  
  391. PrintReport([host + " (stdout)" + " [%s]" % command, "\n"] + listresults + ["\n"])
  392.  
  393.  
  394.  
  395. # If all we see on stderr at this point is our original
  396. # prompt, then then the sudo promotion worked. A bad
  397. # password or bad command will generate additional noise
  398. # from sudo telling us to try again or that there was a
  399. # command error.
  400.  
  401. sudonoise = '' # " ".join(stderr.readline().split(SUDOPROMPT)).strip()
  402.  
  403. if sudonoise: # sudo had problems
  404. PrintReport([host + " [%s]" % command, eCMDFAILURE % (eBADSUDO % sudonoise)] + ["\n"], HANDLER=PrintStderr)
  405. ssh.close()
  406. raise SystemExit
  407. else:
  408.  
  409. stdin, stdout, stderr = ssh.exec_command(command)
  410.  
  411. PrintReport([host + " (stdout)" + " [%s]" % command, "\n"] + stdout.readlines() + ["\n"])
  412.  
  413. if REPORTERR:
  414. PrintReport([host + " (stderr)" + " [%s]" % command, "\n"] + stderr.readlines() + ["\n"], HANDLER=PrintStderr)
  415.  
  416. # Handle aborts
  417. except SystemExit:
  418. ErrorExit(ABORTING)
  419.  
  420. # Catch authentication problems explicitly
  421. except paramiko.AuthenticationException:
  422. PrintReport([host, eCMDFAILURE % eNOLOGIN], HANDLER=PrintStderr)
  423.  
  424. # Everything else is some kind of connection problem
  425.  
  426. except:
  427. PrintReport([host, eCMDFAILURE % (eNOCONNECT % str(sys.exc_info()[1]))], HANDLER=PrintStderr)
  428.  
  429. ssh.close()
  430.  
  431. # End of 'HostCommands()'
  432.  
  433.  
  434. #####
  435. # Print Report
  436. #####
  437.  
  438. # Expects input as [host, success/failure message, result1, result2, ...]
  439. # Uses print handler to stdout by default but can be overriden at call
  440. # time to invoke any arbitrary handler function.
  441.  
  442. def PrintReport(results, HANDLER=PrintStdout):
  443.  
  444. hostname = results[0]
  445. HANDLER(SEPARATOR + hostname +
  446. TRAILER +
  447. (PADWIDTH - len(results[0])) * " " +
  448. results[1])
  449.  
  450. # Prepend the host name if we've asked for noisy reporting
  451.  
  452. hostnoise =""
  453. if NOISY:
  454. hostnoise = HOSTNOISE % hostname
  455.  
  456. for r in results[2:]: # Command Results
  457. HANDLER(hostnoise + INDENTWIDTH * " " + r.strip())
  458.  
  459. # End of 'PrintReport()'
  460.  
  461.  
  462. #####
  463. # Process A File Transfer Request
  464. #####
  465.  
  466. def ProcessTXRQ(request, storage):
  467.  
  468. src_dest = request.split()
  469. if len(src_dest) != 2:
  470. ErrorExit(eBADTXRQ % src_dest)
  471.  
  472. else:
  473.  
  474. if src_dest[0] not in storage:
  475. storage[src_dest[0]] = [src_dest[1],]
  476.  
  477. else:
  478. storage[src_dest[0]].append(src_dest[1])
  479.  
  480. # End of 'ProcessTXRQ'
  481.  
  482.  
  483. #####
  484. # Read File Handling Comments And Directives
  485. #####
  486.  
  487. def ReadFile(fname, envvar, listcontainer, containingfile=""):
  488.  
  489. # Check to see if we can find the file, searching the
  490. # the relevant include environment variable path, if any
  491.  
  492. filename = SearchPath(fname, envvar)
  493. if not filename:
  494. ErrorExit(eBADFILE % fname)
  495.  
  496. # Make sure we don't have a cyclic include reference
  497.  
  498. if filename in FileIncludeStack:
  499. ErrorExit(eINCLUDECYCLE % containingfile + SEPARATOR + filename)
  500.  
  501. else:
  502. FileIncludeStack.append(filename) # Push it on to the stack history
  503.  
  504. try:
  505.  
  506. f = open(filename)
  507. for line in f.readlines():
  508. # Cleanup comments and whitespace
  509.  
  510. line = ConditionLine(line)
  511. # Process variable definitions
  512.  
  513. if line.startswith(DEFINE):
  514. line = line.split(DEFINE)[1]
  515. if line.count(ASSIGN) == 0:
  516. ErrorExit(eBADDEFINE % line)
  517.  
  518. else:
  519.  
  520. name = line.split(ASSIGN)[0].strip()
  521. val = "=".join(line.split(ASSIGN)[1:]).strip()
  522.  
  523. if name:
  524. SymbolTable[name] = val
  525.  
  526. else:
  527. ErrorExit(eBADDEFINE % line)
  528.  
  529. # Process file includes
  530. elif line:
  531. if line.startswith(INCLUDE):
  532. fname = ConditionLine(line.split(INCLUDE)[1])
  533. ReadFile(fname, envvar, listcontainer, containingfile=filename)
  534.  
  535. # It's a normal line - do variable substitution and save
  536. else:
  537. listcontainer.append(VarSub(line))
  538. f.close()
  539.  
  540. FileIncludeStack.pop() # Remove this invocation from the stack
  541. return listcontainer
  542.  
  543. except:
  544. ErrorExit(eBADFILE % filename)
  545.  
  546. # End of 'ReadFile()'
  547.  
  548.  
  549. #####
  550. # Search A Path For A File, Returning First Match
  551. #####
  552.  
  553. def SearchPath(filename, pathlist, delimiter=PATHDELIM):
  554.  
  555. # What we'll return if we find nothing
  556. retval = ""
  557.  
  558. # Handle fully qualified filenames
  559. # But ignore this, if its a directory with a matching name
  560. if os.path.exists(filename) and os.path.isfile(filename):
  561. retval = os.path.realpath(filename)
  562.  
  563. # Find first instance along specified path if one has been specified
  564. elif pathlist:
  565.  
  566. paths = pathlist.split(delimiter)
  567. for path in paths:
  568.  
  569. if path and path[-1] != PATHSEP:
  570. path += PATHSEP
  571.  
  572. path += filename
  573.  
  574. if os.path.exists(path):
  575. retval = os.path.realpath(path)
  576. break
  577. return retval
  578.  
  579. # End of 'SearchPath()'
  580.  
  581.  
  582. #####
  583. # Do Variable Substitution In A String
  584. #####
  585.  
  586. def VarSub(line):
  587.  
  588. for symbol in SymbolTable:
  589. line = line.replace(symbol, SymbolTable[symbol])
  590. return line
  591.  
  592. # End of 'VarSub()'
  593.  
  594.  
  595. # ---------------------- Program Entry Point ---------------------- #
  596.  
  597. #####
  598. # Process Any Options User Set In The Environment Or On Command Line
  599. #####
  600.  
  601. # Handle any options set in the environment
  602.  
  603. OPTIONS = sys.argv[1:]
  604. envopt = os.getenv(PROGENV)
  605. if envopt:
  606. OPTIONS = shlex.split(envopt) + OPTIONS
  607.  
  608. # Combine them with those given on the command line
  609. # This allows the command line to override defaults
  610. # set in the environment
  611.  
  612. try:
  613. opts, args = getopt.getopt(OPTIONS, OPTIONSLIST)
  614.  
  615. except getopt.GetoptError, (errmsg, badarg):
  616. ErrorExit(eBADARG % errmsg)
  617.  
  618. for opt, val in opts:
  619.  
  620.  
  621. if opt == "-E":
  622. REDIRSTDERR = True
  623.  
  624. if opt == "-K":
  625. KEYEXCHANGE = False
  626.  
  627. if opt == "-G":
  628. ProcessTXRQ(val, Get_Transfer_List)
  629.  
  630. if opt == "-H":
  631. Hosts = val.split()
  632. if opt == "-N":
  633. PROMPTUSERNAME = True
  634. KEYEXCHANGE = False
  635. if opt == "-P":
  636. ProcessTXRQ(val, Put_Transfer_List)
  637.  
  638. if opt == "-S":
  639. GETSUDOPW = True
  640.  
  641. if opt == "-T":
  642. try:
  643. TIMEOUT = int(val)
  644. except:
  645. ErrorExit(eBADTIMEOUT)
  646.  
  647. if opt == "-a":
  648. ABORTONFXERROR = False
  649. if opt == "-e":
  650. REPORTERR = False
  651. if opt == "-f":
  652. Commands = ReadFile(val, os.getenv(CMDINCL), Commands)
  653. if opt == "-h":
  654. PrintStdout(USAGE)
  655. sys.exit()
  656. if opt == "-k":
  657. KEYEXCHANGE = True
  658.  
  659. if opt == "-n":
  660. UNAME = val
  661.  
  662. if opt == "-p":
  663. PWORD = val
  664.  
  665. if opt == "-t":
  666. TESTMODE = True
  667.  
  668. if opt == "-v":
  669. PrintStdout(CVSID)
  670. sys.exit()
  671.  
  672. if opt == "-x":
  673. TESTMODE = False
  674.  
  675. if opt == "-y":
  676. NOISY = True
  677.  
  678.  
  679. #####
  680. # Host & Command Line Command Definition Processing
  681. #####
  682.  
  683. # Get the list of hosts if not specified on command line.
  684. # The assumption is that the first argument is the file
  685. # containing the list of targeted hosts and the remaining
  686. # arguments form the command.
  687.  
  688. if not Hosts:
  689.  
  690. # Even if we are only doing file transfers and no command
  691. # is specified, we have to have at least one argument here
  692. # to tell us what hosts we're working on.
  693.  
  694. if not args:
  695. ErrorExit(eNOHOSTS)
  696.  
  697. Hosts = ReadFile(args[0], os.getenv(HOSTINCL), Hosts)
  698. command = " ".join(args[1:])
  699. # If hosts were passed on the command line, all the arguments
  700. # are understood to form the command.
  701. else:
  702.  
  703. # First, do variable substitution on passed hosts
  704.  
  705. for index in range(len(Hosts)):
  706. Hosts[index] = VarSub(Hosts[index])
  707.  
  708. # Now save the command
  709. command = " ".join(args[0:])
  710.  
  711.  
  712.  
  713.  
  714. # Put it in a list data structure because this is what the
  715. # HostCommands() function expects. This is necessary to handle multi
  716. # command input from from a file.
  717.  
  718. command = ConditionLine(command)
  719.  
  720. if command:
  721.  
  722. # Do variable substitution here like any other command
  723. Commands.append(VarSub(command))
  724.  
  725. #####
  726. # Authentication Credential Processing
  727. #####
  728.  
  729. # Precedence of authentication credential sources:
  730. #
  731. # 1) Key exchange
  732. # 2) Forced prompting for name via -N
  733. # 3) Command Line/$TSSHBATCH env variable sets name
  734. # 4) Name picked up from $USER (Default behavior)
  735.  
  736. if not KEYEXCHANGE:
  737.  
  738. # Preset commandline and/or program option variable username takes precedence
  739.  
  740. if not UNAME:
  741. UNAME = os.getenv(USERVAR)
  742. # By default, use the above as the login name and don't prompt for it
  743. # unless overriden on the command line with -N
  744.  
  745. if PROMPTUSERNAME:
  746.  
  747. current_user = UNAME
  748. UNAME = raw_input(pUSER %current_user)
  749. if not UNAME: # User just hit return - wants default
  750. UNAME = current_user
  751.  
  752. # Preset commandline and/or program option variable password takes precedence
  753.  
  754. if not PWORD:
  755. PWORD = getpass.getpass(pPASS)
  756.  
  757. #####
  758. # If Needed, Get sudo Password
  759. ####
  760.  
  761. # The need to prompt for a sudo password depends on a number of
  762. # conditions:
  763. #
  764. # If a login password is present either via manual entry or -p, sudo
  765. # will use that without further prompting. (Default)
  766. #
  767. # The user is prompted for a sudo password under two conditions:
  768. #
  769. # 1) -k option was selected but no password was set with -p
  770. # 2) -S option was selected
  771. #
  772. # If the user IS prompted for a sudo password, any login password
  773. # previously entered - either via -p or interactive entry - will be
  774. # used as the default. The user can hit enter to accept this or enter
  775. # a different password. This allows login and sudo passwords to be
  776. # the same or different.
  777.  
  778. # Find out if we have any sudo commands
  779.  
  780. SUDOPRESENT = False
  781. for command in Commands:
  782. if command.startswith(SUDO + " "):
  783. SUDOPRESENT = True
  784.  
  785. # Check condition 1) above.
  786. # (Condition 2 handled during options processing).
  787.  
  788. if KEYEXCHANGE and not PWORD:
  789. GETSUDOPW = True
  790.  
  791. SUDOPW = PWORD
  792. if SUDOPRESENT and GETSUDOPW:
  793.  
  794. sudopwmsg = pSUDO
  795. if PWORD:
  796. sudopwmsg = sudopwmsg[:-2] + " " + SUDOPWHINT
  797.  
  798. SUDOPW = getpass.getpass(sudopwmsg)
  799. if PWORD and not SUDOPW:
  800. SUDOPW = PWORD
  801.  
  802. #####
  803. # Do The Requested Work
  804. #####
  805.  
  806. # If we're running testmode, just report the final list of
  807. # hosts and commands that would be run
  808.  
  809. if TESTMODE:
  810.  
  811. symtbl = []
  812. gets = []
  813. puts = []
  814.  
  815. # Unroll and format dictionary structures
  816.  
  817. symbols = SymbolTable.keys()
  818. symbols.sort()
  819. for symbol in symbols:
  820. symtbl.append(symbol + (PADWIDTH - len(symbol)) * " "+ SEPARATOR + SymbolTable[symbol])
  821.  
  822. for xfers, unrolled in ((Get_Transfer_List, gets), (Put_Transfer_List, puts)):
  823.  
  824. for source in xfers:
  825. for dest in xfers[source]:
  826. unrolled.append(source + (PADWIDTH*3 - len(source)) * " "+ SEPARATOR + dest)
  827.  
  828. for prompt, description, items in ((TESTRUN, " ".join(OPTIONS), ["\n"]),
  829. (SYMTABLE, "", symtbl + ["\n"]),
  830. (HOSTLIST, "", Hosts + ["\n"]),
  831. (GETFILES, "", gets + ["\n"]),
  832. (PUTFILES, "", puts + ["\n"]),
  833. (COMMANDS, "", Commands + ["\n"])
  834. ):
  835.  
  836. PrintReport([prompt, description] + items)
  837.  
  838.  
  839. # Otherwise, actually do the work by iterating over the list of hosts,
  840. # executing any file transfers and commands. Accomodate commenting
  841. # out hosts in a list.
  842.  
  843. else :
  844.  
  845. for host in Hosts:
  846.  
  847. HostName = host
  848. HostShort = host.split('.')[0]
  849.  
  850. if Get_Transfer_List:
  851. HostFileTransfer(host, UNAME, PWORD, Get_Transfer_List, GET=True)
  852.  
  853. if Put_Transfer_List:
  854. HostFileTransfer(host, UNAME, PWORD, Put_Transfer_List, GET=False)
  855.  
  856. if Commands:
  857. HostCommands(host, UNAME, PWORD, SUDOPW, Commands)