#!/usr/bin/env python
# tsshbatch.py - Non-Interactive ssh Connection
# Copyright (c) 2011-2016 TundraWare Inc.
# Permission Hereby Granted For Unrestricted Personal Or Commercial Use
# See "tsshbatch-license.txt" For Licensing Details
#
# For Updates See: http://www.tundraware.com/Software/tsshbatch
# A tip of the hat for some of the ideas in the program goes to:
#
# http://jessenoller.com/2009/02/05/ssh-programming-with-paramiko-completely-different/
#####
# Version Information - Overwritten by makefile during release process
#####
GITID = '345a9ed tundra Sat Oct 15 16:12:01 2016 -0500'
VERSION = '1.317'
#####
# Program Housekeeping
#####
PROGNAME = "tsshbatch.py"
BASENAME = PROGNAME.split(".py")[0]
PROGENV = BASENAME.upper()
CMDINCL = PROGENV + "CMDS"
HOSTINCL = PROGENV + "HOSTS"
CPRT = "(c)"
PROGDATE = "2011-2016"
OWNER = "TundraWare Inc."
RIGHTS = "All Rights Reserved."
COPYRIGHT = "Copyright %s %s, %s %s" % (CPRT, PROGDATE, OWNER, RIGHTS)
PROGVER = PROGNAME + " " + VERSION + (" - %s" % COPYRIGHT)
HOMEPAGE = "http://www.tundraware.com/Software/%s\n" % BASENAME
#####
# Suppress Deprecation Warnings
# Required in some older environments where paramiko version
# is behind the python libs version.
#####
import warnings
warnings.filterwarnings("ignore", "", DeprecationWarning)
#####
# Imports
#####
import collections
import subprocess
import getopt
import getpass
import os
import paramiko
import shlex
import socket
import sys
import time
#####
# Constants And Literals
#####
ABORTING = 'Aborting ...'
BANNERTIME = 'Elapsed Time: %s Seconds'
BANNERMSG = '%s %s %s On %s At %s' % (PROGNAME, VERSION, '%s', '%s', '%s')
BANNEREND = 'Ended'
BANNERSTART = 'Started'
COMMENT = '#'
COMMANDS = 'Commands'
CFGKEYID = 'identityfile'
CFGREALHOST = 'hostname'
CONSUCCESS = 'SUCCESS: Connection Established'
EXECUTE = '!'
FILEGET = '.getfile'
FILEPUT = '.putfile'
GETFILES = 'Files To GET'
HOSTSEP = '-'
HOSTNOISE = '[%s]'
HOSTLIST = 'Hosts'
INDENTWIDTH = 8
NOMATCH = "No string matches!"
NOTIFICATION = 'NOTIFICATION'
OPTIONSLIST = 'BC:EF:G:H:KLNP:ST:V:Wabef:hi:kl:n:p:qrstvxy'
PADWIDTH = 12
PATHDELIM = ':'
PATHSEP = os.sep
PUTFILES = 'Files To PUT'
SEPARATOR = ' ---> '
STDIN = '-'
SUDO = 'sudo'
SUDOPROMPT = 'READINGSUDOPW'
SUDOARGS = '-S -p %s' % SUDOPROMPT
SUDOPWHINT = '(Default: login password): '
SYMTABLE = 'Global Symbol Table'
TESTRUN = 'Test Run For'
TRAILER = ': '
USERVAR = 'USER'
USAGE = \
PROGVER + "\n" +\
HOMEPAGE + "\n" +\
"Usage: tsshbatch.py [-BEF:KLNSTVWaehkqrstvy -C configfile -G 'file dest' -P 'file dest' -f cmdfile -l logfile -n name -p pw ] -H 'host ...' -i 'hostfile ...' [command arg ... ]\n" +\
" where,\n" +\
"\n" +\
" -B Print start and stop statistics (Off)\n" +\
" -C configfile Specify location of ssh configuration file (~/.ssh/config)\n" +\
" -E Write error output to stdout instead of stderr (Output to stderr)\n" +\
" -F 'string ...' Report host- and command files with matching strings\n" +\
" -K Force password prompting - Overrides previous -k\n" +\
" -G 'file dest' GET file on host and write local dest directory\n" +\
" -H '...' List of targeted hosts passed as a single argument\n" +\
" -L List all known hostfiles and cmdfiles, and exit\n" +\
" -N Force prompting for username\n" +\
" -P 'file dest' PUT local file to host dest directory\n" +\
" -S Force prompting for sudo password\n" +\
" -T seconds Timeout for ssh connection attempts (15 sec)\n" +\
" -V 'string ...' Report host- and command files without matching strings\n" +\
" -W Write list of hosts to stdout and exit\n" +\
" -a Don't abort program after failed file transfers (Abort on failure)\n" +\
" -b Don't abort program after failed sudo command (Abort on failure)\n" +\
" -e Don't report remote host stderr output (Report host stderr) \n" +\
" -f cmdfile Read commands from file\n" +\
" -h Display help\n" +\
" -i 'file file...' Retrieve list of hosts from hostfile. Can be repeated.\n" +\
" -k Use key exchange-based authentication (Use password auth)\n" +\
" -l logfile Log errors to logfile (/dev/null)\n" +\
" -n name Specify login name\n" +\
" -p pw Specify login password\n" +\
" -q Quiet mode - produce less 'noisy' output\n" +\
" -r Suppress reporting of start/stop statistics\n" +\
" -s Silence all program noise - only return command output\n" +\
" -t Run in test mode, don't actually execute commands (Default)\n" +\
" -v Display extended program version information\n" +\
" -x Turn off test mode (if on) and execute requests\n" +\
" -y Turn on 'noisy' reporting for additional detail\n"
#####
# Directives & Related Support
#####
ASSIGN = '='
DEFINE = '.define'
INCLUDE = '.include'
LOCAL = '.local'
NOTIFY = '.notify'
#####
# Builtin Definitions
#####
DATE = '__DATE__'
DATETIME = '__DATETIME__'
HOSTNAME = '__HOSTNAME__'
HOSTNUM = '__HOSTNUM__'
HOSTSHORT = '__HOSTSHORT__'
LOGINNAME = '__LOGINNAME__'
TIME = '__TIME__'
# This is needed to differentiate between user-defined and builtin
# variables later in order to support the user being able to redefine
# them.
BuiltIns = (DATE, DATETIME, HOSTNAME, HOSTNUM, HOSTSHORT, LOGINNAME, TIME)
#####
# Error Messages
#####
eBADARG = "Invalid command line: %s!"
eBADEXEC = "Execution variable failed: %s"
eBADFILE = "Cannot open '%s'!"
eBADSUDO = "sudo Failed (Check Password Or Command!) sudo Error Report: %s"
eBADTXRQ = "Bad Transfer Request: %s Must Have Exactly 1 Source And 1 Destination!"
eBADDEFINE = "Bad Symbol Definition: %s"
eBADTIMEOUT = "Timeout Value Must Be an Integer!"
eCMDFAILURE = "Failed To Run Command(s): %s"
eFXERROR = "File Transfer Error: %s"
eINCLUDECYCLE = "Circular Include At: %s"
eNOCONNECT = "Cannot Connect: %s"
eNOHOSTS = "No Hosts Specified!"
eNOLOGIN = "Cannot Login! (Login/Password Bad?)"
#####
# Informational Messages
#####
iCMDFILES = "Command Files:\n==============\n"
iHOSTFILES = "Host Files:\n===========\n"
iNOCFGFILE = "Warning: Cannot Open Configuration File '%s'! Continuing Anyway ..."
iNOPATH = "Warning: Cannot Open Path '%s'!"
iTXFILE = "Writing %s To %s ..."
#####
# Prompts
#####
pPASS = "Password: "
pSUDO = "%s Password: " % SUDO
pUSER = "Username (%s): "
#####
# Options That Can Be Overriden By User
####
ABORTBADSUDO = True # Abort after a sudo promotion error
ABORTONFXERROR = True # Abort after a file transfer error
BANNERSON = False # Print start/stop banner info
GETSUDOPW = False # Prompt for sudo password
Hosts = [] # List of hosts to target
KEYEXCHANGE = False # Do key exchange-based auth?
LOGFILE = "/dev/null" # Where paramiko logging output goes
PROMPTUSERNAME = False # Don't use $USER, prompt for username
PWORD = "" # Password
REDIRSTDERR = False # Redirect stderr to stdout
REPORTERR = True # Report stderr output from remote host
SSHCFGFILE = "~/.ssh/config" # Default ssh configuration file location
TESTMODE = True # Run program in test mode, don't actually execute commands
TIMEOUT = 15 # Connection attempt timeout (sec)
UNAME = "" # Login name
WRITEINVENTORY = False # Write list of selected hosts to stdout as a single line
# Noise levels
NOISELEVEL = 0 # Normal noisy
SILENT = 1 # No program noise at all
QUIET = 2 # Make output less noisy
NOISY = 3 # Print output with extra detail
#####
# Global Data Structures & Variables
#####
Commands = []
FileIncludeStack = []
Get_Transfer_List = collections.OrderedDict()
Put_Transfer_List = collections.OrderedDict()
SSH_Configuration = {}
GlobalSymbolTable = {}
#####
# Functions
#####
#####
# Gets rid of comments and strips leading/trailing whitespace
#####
def ConditionLine(line):
return line.split(COMMENT)[0].strip()
# End of 'ConditionLine()'
#####
# Return List Of All Files Found On A String Of Paths
####
def FindFilesOnPath(pathenv):
plist = os.getenv(pathenv) or ''
slist = plist.split(PATHDELIM)
found = []
if slist:
for p in slist:
if p:
if not p.endswith(PATHSEP):
p += PATHSEP
try:
found += [ p+x for x in os.listdir(p)]
except:
if NOISELEVEL not in (QUIET, SILENT):
PrintStderr(iNOPATH % p)
return found
# End of 'FindFilesOnPath()'
####
# Get An Execution Variable Value
###
def GetExecutionVariable(cmd):
try:
origcmd = cmd
status, cmd = subprocess.getstatusoutput(cmd.strip())
# Blow out if the command failed
if status:
raise
except:
PrintReport([PROGNAME, eBADEXEC % origcmd], HANDLER=PrintStderr)
ErrorExit("")
# Return successfully
return cmd
# End of 'GetExecutionVariable()'
#####
# Check To See If A Key Exists In A String, Excluding Quoted Substrings
#####
def KeyInString(key, string):
""" Look for 'key' in 'string', but exclude segments of the string
that are contained within single- or double quotes.
"""
quote_chars = ('"', "'")
InLiteral = False
search = ""
index = 0
while index < len(string):
char = string[index]
if InLiteral:
if char == quote_char:
InLiteral = False
elif char in quote_chars:
quote_char = char
InLiteral = True
else:
search += char
index += 1
status = False
if search.count(key) > 0:
status = True
return status
# End of KeyInString
#####
# Print Message(s) To stderr
#####
def PrintStderr(msg, EOL="\n"):
# If we've been told to redirect to stdout, do so instead
if REDIRSTDERR:
PrintStdout(msg, EOL)
else:
sys.stderr.write(msg + EOL)
sys.stderr.flush()
# End of 'PrintStderr()'
#####
# Print Message(s) To stdout
#####
def PrintStdout(msg, EOL="\n"):
sys.stdout.write(msg + EOL)
sys.stdout.flush()
# End of 'PrintStdout()'
#####
# Display An Error Message And Exit
#####
def ErrorExit(msg):
if msg:
PrintStderr(msg)
# If requested, print banner
if BANNERSON:
PrintStdout(BANNERMSG % (BANNERSTART, time.strftime("%Y-%m-%d"), time.strftime("%H:%M:%S")))
PrintStdout(BANNERTIME % float(time.time() - StartTime))
os._exit(1)
# End Of 'ErrorExit()'
#####
# Transfer Files To A Host
#####
def HostFileTransfer(host, user, pw, filelist, GET=False):
# Get an sftp connection and move files
try:
ssh = SSH_Connect(host, user, pw, TIMEOUT)
sftp = ssh.open_sftp()
for src in filelist:
# Process any .define substitions
srcfile = VarSub(src)
for destdir in filelist[src]:
# Process any .define substitutions
destdir = VarSub(destdir)
# Make sure we have a trailing path separator
destination = destdir
if destination[-1] != PATHSEP:
destination += PATHSEP
try:
if GET:
destination += host + HOSTSEP + os.path.basename(srcfile)
if NOISELEVEL != SILENT:
PrintStdout(iTXFILE % (host + ":" + srcfile, destination))
sftp.get(srcfile, destination)
os.chmod(destination, sftp.stat(srcfile).st_mode)
else:
destination += os.path.basename(srcfile)
if NOISELEVEL != SILENT:
PrintStdout(iTXFILE % (srcfile, host + ":" + destination))
sftp.put(srcfile, destination)
sftp.chmod(destination, os.stat(srcfile).st_mode)
except:
PrintReport([host, eFXERROR % str(sys.exc_info()[1])], HANDLER=PrintStderr)
# Do we continue after failed file transfers or not?
if ABORTONFXERROR:
try:
sftp.close()
ssh.close()
except:
pass
ErrorExit("")
sftp.close()
ssh.close()
except:
PrintReport([host, eFXERROR % str(sys.exc_info()[1])], HANDLER=PrintStderr)
# Do we continue after failed connection attempts or not?
if ABORTONFXERROR:
try:
sftp.close()
ssh.close()
except:
pass
ErrorExit("")
# End of 'HostFileTransfer()'
def HostCommands(host, user, pw, sudopw, commands):
# Figure out if we want report formatting
Format = True
if NOISELEVEL == SILENT:
Format = False
# Connect and run the command, reporting results as we go
try:
ssh = SSH_Connect(host, user, pw, TIMEOUT)
if NOISELEVEL != QUIET:
PrintReport([host, CONSUCCESS], FORMAT=Format)
# Run all requested commands
for command in commands:
# Dereference variables
command = VarSub(command)
# It's possible to get blank lines from stdin.
# Ignore them.
if not command:
continue
# Process notifications - disabled in SILENT mode
if command.startswith(NOTIFY):
if NOISELEVEL != SILENT:
PrintStdout("%s> %s\n" % (NOTIFICATION, " ".join(command.split(NOTIFY)).strip()))
continue
# If this is a sudo run, force password to be read
# from stdin thereby avoiding fiddling around with ptys.
if KeyInString(SUDO + " ", command):
command = command.replace(SUDO, "%s %s" % (SUDO, SUDOARGS), 1)
stdin, stdout, stderr = ssh.exec_command(command)
# If doing a sudo command, send the password
if KeyInString(SUDO + " ", command):
stdin.write("%s\n" % sudopw)
stdin.flush()
# If all we see on stderr at this point is our original
# prompt, then then the sudo promotion worked. A bad
# password or bad command will generate additional noise
# from sudo telling us to try again or that there was a
# command error.
sudonoise = " ".join(stderr.readline().split(SUDOPROMPT)).strip()
if sudonoise: # sudo had problems
PrintReport([host + " [%s]" % command, eCMDFAILURE % (eBADSUDO % sudonoise)] + ["\n"], HANDLER=PrintStderr)
# Abort program on sudo failure. This is default behavior
# but can be overriden on the command line.
if ABORTBADSUDO:
ssh.close()
raise SystemExit
else:
break
cmdreport = " [%s]" % command
if NOISELEVEL == QUIET:
cmdreport = ""
PrintReport([host + " (stdout)" + cmdreport, "\n"] + stdout.readlines() + ["\n"], FORMAT=Format)
if REPORTERR:
PrintReport([host + " (stderr)" + cmdreport, "\n"] + stderr.readlines() + ["\n"], HANDLER=PrintStderr, FORMAT=Format)
# Handle aborts
except SystemExit:
ErrorExit(ABORTING)
# Catch authentication problems explicitly
except paramiko.AuthenticationException:
PrintReport([host, eCMDFAILURE % eNOLOGIN], HANDLER=PrintStderr)
# Everything else is some kind of connection problem
except:
PrintReport([host, eCMDFAILURE % (eNOCONNECT % str(sys.exc_info()[1]))], HANDLER=PrintStderr)
# Close any remaining ssh connection
try:
ssh.close()
except:
pass
# End of 'HostCommands()'
#####
# Print Report
#####
# Expects input as [host, success/failure message, result1, result2, ...]
# Uses print handler to stdout by default but can be overriden at call
# time to invoke any arbitrary handler function.
def PrintReport(results, HANDLER=PrintStdout, FORMAT=True):
if FORMAT:
hostname = results[0]
HANDLER(SEPARATOR + hostname +
TRAILER +
(PADWIDTH - len(results[0])) * " " +
results[1])
# Prepend the host name if we've asked for noisy reporting
hostnoise =""
if NOISELEVEL == NOISY:
hostnoise = HOSTNOISE % hostname
for r in results[2:]: # Command Results
HANDLER(hostnoise + INDENTWIDTH * " " + r.strip())
# In (silent) unformatted mode we just return results with no
# headers or formatting. We suppress the last line in the list.
# It is an empty line introduced for formatting by the caller, but
# isn't really part of the returned output.
else:
for r in results[2:-1]:
HANDLER(r, EOL="")
# End of 'PrintReport()'
#####
# Process A File Transfer Request
#####
def ProcessTXRQ(request, storage):
src_dest = request.split()
if len(src_dest) != 2:
ErrorExit(eBADTXRQ % src_dest)
else:
if src_dest[0] not in storage:
storage[src_dest[0]] = [src_dest[1],]
else:
storage[src_dest[0]].append(src_dest[1])
# End of 'ProcessTXRQ'
#####
# Read File Handling Comments And Directives
#####
def ReadFile(fname, envvar, listcontainer, containingfile=""):
LocalSymbolTable = {}
# Check to see if we can find the file, searching the
# the relevant include environment variable path, if any
filename = SearchPath(fname, envvar)
if not filename:
ErrorExit(eBADFILE % fname)
# Make sure we don't have a cyclic include reference
if filename in FileIncludeStack:
ErrorExit(eINCLUDECYCLE % containingfile + SEPARATOR + filename)
else:
FileIncludeStack.append(filename) # Push it on to the stack history
# Line parsing starts here
try:
f = open(filename)
for line in f.readlines():
# Cleanup comments and whitespace
line = ConditionLine(line)
# Process file transfer requests
if line.startswith(FILEGET):
val = line.split(FILEGET)[1].strip()
ProcessTXRQ(val, Get_Transfer_List)
elif line.startswith(FILEPUT):
val = line.split(FILEPUT)[1].strip()
ProcessTXRQ(val, Put_Transfer_List)
# Process local variable definitions
elif line.startswith(LOCAL):
line = line.split(LOCAL)[1]
if line.count(ASSIGN) == 0:
ErrorExit(eBADDEFINE % line)
else:
name = line.split(ASSIGN)[0].strip()
val = "=".join(line.split(ASSIGN)[1:]).strip()
if name:
# Process references to execution variables
if val.startswith(EXECUTE):
val = GetExecutionVariable(val[1:])
LocalSymbolTable[name] = val
else:
ErrorExit(eBADDEFINE % line)
# Process global variable definitions
elif line.startswith(DEFINE):
line = line.split(DEFINE)[1]
if line.count(ASSIGN) == 0:
ErrorExit(eBADDEFINE % line)
else:
name = line.split(ASSIGN)[0].strip()
val = "=".join(line.split(ASSIGN)[1:]).strip()
if name:
# Process references to execution variables
if val.startswith(EXECUTE):
val = GetExecutionVariable(val[1:])
GlobalSymbolTable[name] = val
else:
ErrorExit(eBADDEFINE % line)
# Process file includes
elif line:
if line.startswith(INCLUDE):
fname = VarSub(ConditionLine(line.split(INCLUDE)[1]))
ReadFile(fname, envvar, listcontainer, containingfile=filename)
# It's a normal line - do local variable substitution and save
else:
listcontainer.append(VarSub(line, Table=LocalSymbolTable))
f.close()
FileIncludeStack.pop() # Remove this invocation from the stack
return listcontainer
except:
ErrorExit(eBADFILE % filename)
# End of 'ReadFile()'
#####
# Setup An ssh Connection
#####
def SSH_Connect(host, user, pw, time):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
keyfiles = None
if host in SSH_Configuration:
entry = SSH_Configuration[host]
keyfiles, host = entry[0], entry[1]
if KEYEXCHANGE:
ssh.connect(host, username=user, key_filename=keyfiles, timeout=time)
else:
ssh.connect(host, username=user, password=pw, allow_agent=False, look_for_keys=False, timeout=time)
return ssh
# End of 'SSH_Connect()'
#####
# Read Any Local SSH Configuration
#####
def SSH_GetConfig(filename):
# NOTE: We only support the "IdentityFile" and "HostName"
# configuration directives here.
retval = {}
sshconfig = paramiko.SSHConfig()
try:
f = open(os.path.expanduser(filename))
# File open failed, but we go on anyway
except:
PrintStderr(iNOCFGFILE % filename)
return retval
sshconfig.parse(f)
f.close()
# Return local configuration as a dictonary with hostnames as keys
for host in sshconfig.get_hostnames():
if host != "*":
cfgentry = sshconfig.lookup(host)
output = []
keyfiles = []
if CFGKEYID in cfgentry:
keyfiles = cfgentry[CFGKEYID]
realhost = host
if CFGREALHOST in cfgentry:
realhost = cfgentry[CFGREALHOST]
retval[host] = [keyfiles, realhost]
return retval
# End of 'SSH_GetConfig()'
#####
# Search A Path For A File, Returning First Match
#####
def SearchPath(filename, pathlist, delimiter=PATHDELIM):
# What we'll return if we find nothing
retval = ""
# Handle fully qualified filenames
# But ignore this, if its a directory with a matching name
if os.path.exists(filename) and os.path.isfile(filename):
retval = os.path.realpath(filename)
# Find first instance along specified path if one has been specified
elif pathlist:
paths = pathlist.split(delimiter)
for path in paths:
if path and path[-1] != PATHSEP:
path += PATHSEP
path += filename
if os.path.exists(path):
retval = os.path.realpath(path)
break
return retval
# End of 'SearchPath()'
#####
# Do Variable Substitution In A String
#####
def VarSub(line, Table=GlobalSymbolTable):
for symbol in Table:
line = line.replace(symbol, Table[symbol])
return line
# End of 'VarSub()'
# ---------------------- Program Entry Point ---------------------- #
#####
# Note starting time
#####
StartTime = time.time() # Need this here in case we error abort
# on command line processing. Gets
# reset below for normal processing.
#####
# Process Any Options User Set In The Environment Or On Command Line
#####
# Handle any options set in the environment
OPTIONS = sys.argv[1:]
envopt = os.getenv(PROGENV)
if envopt:
OPTIONS = shlex.split(envopt) + OPTIONS
# Combine them with those given on the command line
# This allows the command line to override defaults
# set in the environment
try:
opts, args = getopt.getopt(OPTIONS, OPTIONSLIST)
except getopt.GetoptError as xxx_todo_changeme:
(errmsg, badarg) = xxx_todo_changeme.args
ErrorExit(eBADARG % errmsg)
for opt, val in opts:
if opt == "-B":
BANNERSON = True
if opt == "-C":
SSHCFGFILE = val
if opt == "-E":
REDIRSTDERR = True
if ( opt == "-F" or opt == "-V"):
# Look for string matches or misses in
# the host- and command files
files = FindFilesOnPath(HOSTINCL)
files += FindFilesOnPath(CMDINCL)
for fn in files:
f = open(fn, 'r')
content = f.readlines()
f.close()
linenum = 0
matched = False
for line in content:
linenum += 1
for string in val.split():
if string.lower() in line.lower():
if opt == "-F":
PrintStdout("%s:%s %s" % (fn, linenum, line.strip()))
matched = True
break
# Handle non-matches if we asked for that
if (not matched and opt == '-V'):
PrintStdout("%s: %s" % (fn, NOMATCH))
sys.exit()
if opt == "-K":
KEYEXCHANGE = False
if opt == "-G":
ProcessTXRQ(val, Get_Transfer_List)
if opt == "-H":
Hosts += val.split()
if opt == "-L":
# List all the host- and command files
for hdr, pathenv in [[iHOSTFILES, HOSTINCL],
[iCMDFILES, CMDINCL]]:
PrintStdout(hdr)
PrintStdout("\n".join(FindFilesOnPath(pathenv)), EOL="\n\n")
sys.exit()
if opt == "-N":
PROMPTUSERNAME = True
KEYEXCHANGE = False
if opt == "-P":
ProcessTXRQ(val, Put_Transfer_List)
if opt == "-S":
GETSUDOPW = True
if opt == "-T":
try:
TIMEOUT = int(val)
except:
ErrorExit(eBADTIMEOUT)
if opt == "-W":
WRITEINVENTORY = True
if opt == "-a":
ABORTONFXERROR = False
if opt == "-b":
ABORTBADSUDO = False
if opt == "-e":
REPORTERR = False
if opt == "-f":
Commands = ReadFile(val, os.getenv(CMDINCL), Commands)
if opt == "-h":
PrintStdout(USAGE)
sys.exit()
if opt == "-i":
for hostfile in val.split():
Hosts = ReadFile(hostfile, os.getenv(HOSTINCL), Hosts)
if opt == "-k":
KEYEXCHANGE = True
if opt == "-l":
LOGFILE = val
if opt == "-n":
UNAME = val
if opt == "-p":
PWORD = val
if opt == "-q":
NOISELEVEL = QUIET
if opt == "-r":
BANNERSON = False
if opt == "-s":
NOISELEVEL = SILENT
if opt == "-t":
TESTMODE = True
if opt == "-v":
PrintStdout(GITID)
sys.exit()
if opt == "-x":
TESTMODE = False
if opt == "-y":
NOISELEVEL = NOISY
#####
# Intitialize paramiko Logging
#####
paramiko.util.log_to_file(LOGFILE)
#####
# Command Line Command Definition Processing
#####
# Must have a list of hosts or we cannot go on with any
# operation whether file xfer or command execution
if not Hosts:
ErrorExit(eNOHOSTS)
else:
command = " ".join(args[0:])
# Put it in a list data structure because this is what the
# HostCommands() function expects. This is necessary to handle multi
# command input from from a file.
command = ConditionLine(command)
if command:
# Do variable substitution here like any other command
Commands.append(command)
#####
# Authentication Credential Processing
#####
# Precedence of authentication credential sources:
#
# 1) Key exchange
# 2) Forced prompting for name via -N
# 3) Command Line/$TSSHBATCH env variable sets name
# 4) Name picked up from $USER (Default behavior)
# Regardless of authorization type, we need a user name.
# Preset commandline and/or program option variable username takes
# precedence
if not UNAME:
UNAME = os.getenv(USERVAR)
# By default, use the above as the login name and don't prompt for it
# unless overriden on the command line with -N
if PROMPTUSERNAME:
current_user = UNAME
UNAME = input(pUSER %current_user)
if not UNAME: # User just hit return - wants default
UNAME = current_user
# For password auth, preset commandline and/or program option
# variable password takes precedence
if not KEYEXCHANGE and not PWORD:
PWORD = getpass.getpass(pPASS)
#####
# If Needed, Get sudo Password
####
# The need to prompt for a sudo password depends on a number of
# conditions:
#
# If a login password is present either via manual entry or -p, sudo
# will use that without further prompting. (Default)
#
# The user is prompted for a sudo password under two conditions:
#
# 1) -k option was selected but no password was set with -p
# 2) -S option was selected
#
# If the user IS prompted for a sudo password, any login password
# previously entered - either via -p or interactive entry - will be
# used as the default. The user can hit enter to accept this or enter
# a different password. This allows login and sudo passwords to be
# the same or different.
# Find out if we have any sudo commands
SUDOPRESENT = False
for command in Commands:
if KeyInString(SUDO + " ", command):
SUDOPRESENT = True
# Check condition 1) above.
# (Condition 2 handled during options processing).
if KEYEXCHANGE and not PWORD:
GETSUDOPW = True
SUDOPW = PWORD
if SUDOPRESENT and GETSUDOPW:
sudopwmsg = pSUDO
if PWORD:
sudopwmsg = sudopwmsg[:-2] + " " + SUDOPWHINT
SUDOPW = getpass.getpass(sudopwmsg)
if PWORD and not SUDOPW:
SUDOPW = PWORD
# Report time statistics if requested
if BANNERSON:
StartTime= time.time() # The the real starting time - the time the work began
PrintStdout(BANNERMSG % (BANNERSTART, time.strftime("%Y-%m-%d"), time.strftime("%H:%M:%S")))
#####
# Do The Requested Work
#####
# If we're running testmode, just report the final list of
# hosts and commands that would be run
if TESTMODE:
symtbl = []
gets = []
puts = []
# Unroll and format dictionary structures
symbols = list(GlobalSymbolTable.keys())
symbols.sort()
for symbol in symbols:
symtbl.append(symbol + (PADWIDTH - len(symbol)) * " "+ SEPARATOR + GlobalSymbolTable[symbol])
for xfers, unrolled in ((Get_Transfer_List, gets), (Put_Transfer_List, puts)):
for source in xfers:
for dest in xfers[source]:
# Dereference any variables in the file transfer specification
source = VarSub(source)
dest = VarSub(dest)
unrolled.append(source + (PADWIDTH*3 - len(source)) * " "+ SEPARATOR + dest)
# Dereference any variables in the list of commands
index = 0
while (index < len(Commands)):
Commands[index] = VarSub(Commands[index])
index += 1
# Dereference any variables in the list of hosts
index = 0
while (index < len(Hosts)):
Hosts[index] = VarSub(Hosts[index])
index += 1
# Write the inventory out if that's all that was asked for
if WRITEINVENTORY:
PrintStdout(" ".join(Hosts))
# Otherwise, print the summary
else:
for prompt, description, items in ((TESTRUN, " ".join(OPTIONS), ["\n"]),
(SYMTABLE, "", symtbl + ["\n"]),
(HOSTLIST, "", Hosts + ["\n"]),
(GETFILES, "", gets + ["\n"]),
(PUTFILES, "", puts + ["\n"]),
(COMMANDS, "", Commands + ["\n"])
):
PrintReport([prompt, description] + items)
# Otherwise, actually do the work by iterating over the list of hosts,
# executing any file transfers and commands. Accomodate commenting
# out hosts in a list.
else :
# Pick up any local configuration data
SSH_Configuration = SSH_GetConfig(SSHCFGFILE)
# Check to see if user is trying to override any builtins
protected = []
for builtin in BuiltIns:
if builtin in GlobalSymbolTable:
protected.append(builtin)
# Now iterate over requested hosts
hostnum = 0
for host in Hosts:
# Update the host counter
hostnum += 1
# Add internally generated symbols to the symbol table.
# That way, both user-defined and builtin symbols will
# subsequently be substituted.
# Find out the effective name of the user doing this
uname = UNAME
if not uname:
uname = os.getenv(USERVAR)
internals = [
(DATE, time.strftime("%Y%m%d")),
(DATETIME, time.strftime("%Y%m%d%H%M%S")),
(HOSTNAME, host),
(HOSTNUM, str(hostnum)),
(HOSTSHORT, host.split('.')[0]),
(LOGINNAME, uname),
(TIME, time.strftime("%H%M%S")),
]
# Install builtins in the symbol table but only if the
# user isn't overriding them.
for symbol, value in internals:
if symbol not in protected:
GlobalSymbolTable[symbol] = value
# Apply any relevant variable dereferencing
host = VarSub(host)
if Get_Transfer_List:
HostFileTransfer(host, UNAME, PWORD, Get_Transfer_List, GET=True)
if Put_Transfer_List:
HostFileTransfer(host, UNAME, PWORD, Put_Transfer_List, GET=False)
if Commands:
HostCommands(host, UNAME, PWORD, SUDOPW, Commands)
#####
# If requested, print startup banner
#####
if BANNERSON:
PrintStdout(BANNERMSG % (BANNEREND, time.strftime("%Y-%m-%d"), time.strftime("%H:%M:%S")))
PrintStdout(BANNERTIME % float(time.time() - StartTime))