0 0 1
1.317 default
Jean-Francois Pieronne - 3 years ago 2016-11-30 15:39:37
jf.pieronne@laposte.net
tsshbatch.py initial version
1 file changed with 1320 insertions and 0 deletions:
↑ Collapse Diff ↑
 
new file 100755
1
 
#!/usr/bin/env python
2
 
# tsshbatch.py - Non-Interactive ssh Connection
3
 
# Copyright (c) 2011-2016 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
 
# Version Information - Overwritten by makefile during release process
15
 
#####
16
 

	
17
 
GITID      = '345a9ed tundra Sat Oct 15 16:12:01 2016 -0500'
18
 
VERSION    = '1.317'
19
 

	
20
 

	
21
 
#####
22
 
# Program Housekeeping
23
 
#####
24
 

	
25
 
PROGNAME     = "tsshbatch.py"
26
 
BASENAME     = PROGNAME.split(".py")[0]
27
 
PROGENV      = BASENAME.upper()
28
 
CMDINCL      = PROGENV + "CMDS"
29
 
HOSTINCL     = PROGENV + "HOSTS"
30
 

	
31
 
CPRT         = "(c)"
32
 
PROGDATE     = "2011-2016"
33
 
OWNER        = "TundraWare Inc."
34
 
RIGHTS       = "All Rights Reserved."
35
 
COPYRIGHT    = "Copyright %s %s, %s  %s" % (CPRT, PROGDATE, OWNER, RIGHTS)
36
 

	
37
 
PROGVER      = PROGNAME + " " + VERSION + (" - %s" % COPYRIGHT)
38
 
HOMEPAGE     = "http://www.tundraware.com/Software/%s\n" % BASENAME
39
 

	
40
 

	
41
 
#####
42
 
# Suppress Deprecation Warnings
43
 
# Required in some older environments where paramiko version
44
 
# is behind the python libs version.
45
 
#####
46
 

	
47
 
import warnings
48
 
warnings.filterwarnings("ignore", "", DeprecationWarning)
49
 

	
50
 

	
51
 
#####
52
 
# Imports
53
 
#####
54
 

	
55
 
import collections
56
 
import commands
57
 
import getopt
58
 
import getpass
59
 
import os
60
 
import paramiko
61
 
import shlex
62
 
import socket
63
 
import sys
64
 
import time
65
 

	
66
 

	
67
 
#####
68
 
# Constants And Literals
69
 
#####
70
 

	
71
 

	
72
 
ABORTING     = 'Aborting ...'
73
 
BANNERTIME   = 'Elapsed Time: %s Seconds'
74
 
BANNERMSG    = '%s %s %s On %s At %s' % (PROGNAME, VERSION, '%s', '%s', '%s')
75
 
BANNEREND    = 'Ended'
76
 
BANNERSTART  = 'Started'
77
 
COMMENT      = '#'
78
 
COMMANDS     = 'Commands'
79
 
CFGKEYID     = 'identityfile'
80
 
CFGREALHOST  = 'hostname'
81
 
CONSUCCESS   = 'SUCCESS: Connection Established'
82
 
EXECUTE      = '!'
83
 
FILEGET      = '.getfile'
84
 
FILEPUT      = '.putfile'
85
 
GETFILES     = 'Files To GET'
86
 
HOSTSEP      = '-'
87
 
HOSTNOISE    = '[%s]'
88
 
HOSTLIST     = 'Hosts'
89
 
INDENTWIDTH  = 8
90
 
NOMATCH      = "No string matches!"
91
 
NOTIFICATION = 'NOTIFICATION'
92
 
OPTIONSLIST  = 'BC:EF:G:H:KLNP:ST:V:Wabef:hi:kl:n:p:qrstvxy'
93
 
PADWIDTH     = 12
94
 
PATHDELIM    = ':'
95
 
PATHSEP      = os.sep
96
 
PUTFILES     = 'Files To PUT'
97
 
SEPARATOR    = ' --->  '
98
 
STDIN        = '-'
99
 
SUDO         = 'sudo'
100
 
SUDOPROMPT   = 'READINGSUDOPW'
101
 
SUDOARGS     = '-S -p %s' % SUDOPROMPT
102
 
SUDOPWHINT   = '(Default: login password): '
103
 
SYMTABLE     = 'Global Symbol Table'
104
 
TESTRUN      = 'Test Run For'
105
 
TRAILER      = ': '
106
 
USERVAR      = 'USER'
107
 

	
108
 
USAGE        = \
109
 
    PROGVER  + "\n"                                                                                     +\
110
 
    HOMEPAGE + "\n"                                                                                     +\
111
 
    "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" +\
112
 
    "          where,\n"                                                                                +\
113
 
    "\n"                                                                                                +\
114
 
    "            -B                  Print start and stop statistics (Off)\n"                               +\
115
 
    "            -C configfile       Specify location of ssh configuration file (~/.ssh/config)\n"          +\
116
 
    "            -E                  Write error output to stdout instead of stderr (Output to stderr)\n"   +\
117
 
    "            -F 'string ...'     Report host- and command files with matching strings\n"                +\
118
 
    "            -K                  Force password prompting - Overrides previous -k\n"                    +\
119
 
    "            -G 'file dest'      GET file on host and write local dest directory\n"                     +\
120
 
    "            -H '...'            List of targeted hosts passed as a single argument\n"                  +\
121
 
    "            -L                  List all known hostfiles and cmdfiles, and exit\n"                     +\
122
 
    "            -N                  Force prompting for username\n"                                        +\
123
 
    "            -P 'file dest'      PUT local file to host dest directory\n"                               +\
124
 
    "            -S                  Force prompting for sudo password\n"                                   +\
125
 
    "            -T seconds          Timeout for ssh connection attempts (15 sec)\n"                        +\
126
 
    "            -V 'string ...'     Report host- and command files without matching strings\n"             +\
127
 
    "            -W                  Write list of hosts to stdout and exit\n"                              +\
128
 
    "            -a                  Don't abort program after failed file transfers (Abort on failure)\n"  +\
129
 
    "            -b                  Don't abort program after failed sudo command (Abort on failure)\n"    +\
130
 
    "            -e                  Don't report remote host stderr output (Report host stderr) \n"        +\
131
 
    "            -f cmdfile          Read commands from file\n"                                             +\
132
 
    "            -h                  Display help\n"                                                        +\
133
 
    "            -i 'file file...'   Retrieve list of hosts from hostfile.  Can be repeated.\n"             +\
134
 
    "            -k                  Use key exchange-based authentication (Use password auth)\n"           +\
135
 
    "            -l logfile          Log errors to logfile (/dev/null)\n"                                   +\
136
 
    "            -n name             Specify login name\n"                                                  +\
137
 
    "            -p pw               Specify login password\n"                                              +\
138
 
    "            -q                  Quiet mode - produce less 'noisy' output\n"                            +\
139
 
    "            -r                  Suppress reporting of start/stop statistics\n"                         +\
140
 
    "            -s                  Silence all program noise - only return command output\n"              +\
141
 
    "            -t                  Run in test mode, don't actually execute commands (Default)\n"         +\
142
 
    "            -v                  Display extended program version information\n"                        +\
143
 
    "            -x                  Turn off test mode (if on) and execute requests\n"                     +\
144
 
    "            -y                  Turn on 'noisy' reporting for additional detail\n"
145
 

	
146
 

	
147
 
#####
148
 
# Directives & Related Support
149
 
#####
150
 

	
151
 
ASSIGN      = '='
152
 
DEFINE      = '.define'
153
 
INCLUDE     = '.include'
154
 
LOCAL       = '.local'
155
 
NOTIFY      = '.notify'
156
 

	
157
 

	
158
 
#####
159
 
# Builtin Definitions
160
 
#####
161
 

	
162
 
DATE        = '__DATE__'
163
 
DATETIME    = '__DATETIME__'
164
 
HOSTNAME    = '__HOSTNAME__'
165
 
HOSTNUM     = '__HOSTNUM__'
166
 
HOSTSHORT   = '__HOSTSHORT__'
167
 
LOGINNAME   = '__LOGINNAME__'
168
 
TIME        = '__TIME__'
169
 

	
170
 

	
171
 
# This is needed to differentiate between user-defined and builtin
172
 
# variables later in order to support the user being able to redefine
173
 
# them.
174
 

	
175
 
BuiltIns    = (DATE, DATETIME, HOSTNAME, HOSTNUM, HOSTSHORT, LOGINNAME, TIME)
176
 

	
177
 

	
178
 
#####
179
 
# Error Messages
180
 
#####
181
 

	
182
 
eBADARG       =  "Invalid command line: %s!"
183
 
eBADEXEC      =  "Execution variable failed: %s"
184
 
eBADFILE      =  "Cannot open '%s'!"
185
 
eBADSUDO      =  "sudo Failed (Check Password Or Command!) sudo Error Report:  %s"
186
 
eBADTXRQ      =  "Bad Transfer Request: %s  Must Have Exactly 1 Source And 1 Destination!"
187
 
eBADDEFINE    =  "Bad Symbol Definition: %s"
188
 
eBADTIMEOUT   =  "Timeout Value Must Be an Integer!"
189
 
eCMDFAILURE   =  "Failed To Run Command(s): %s"
190
 
eFXERROR      =  "File Transfer Error: %s"
191
 
eINCLUDECYCLE =  "Circular Include At: %s"
192
 
eNOCONNECT    =  "Cannot Connect: %s"
193
 
eNOHOSTS      =  "No Hosts Specified!"
194
 
eNOLOGIN      =  "Cannot Login! (Login/Password Bad?)"
195
 

	
196
 

	
197
 
#####
198
 
# Informational Messages
199
 
#####
200
 

	
201
 
iCMDFILES     = "Command Files:\n==============\n"
202
 
iHOSTFILES    = "Host Files:\n===========\n"
203
 
iNOCFGFILE    = "Warning: Cannot Open Configuration File '%s'! Continuing Anyway ..."
204
 
iNOPATH       = "Warning: Cannot Open Path '%s'!"
205
 
iTXFILE       = "Writing %s To %s ..."
206
 

	
207
 

	
208
 
#####
209
 
# Prompts
210
 
#####
211
 

	
212
 
pPASS = "Password: "
213
 
pSUDO = "%s Password: " % SUDO
214
 
pUSER = "Username (%s): "
215
 

	
216
 

	
217
 
#####
218
 
# Options That Can Be Overriden By User
219
 
####
220
 

	
221
 
ABORTBADSUDO   = True            # Abort after a sudo promotion error
222
 
ABORTONFXERROR = True            # Abort after a file transfer error
223
 
BANNERSON      = False           # Print start/stop banner info
224
 
GETSUDOPW      = False           # Prompt for sudo password
225
 
Hosts          = []              # List of hosts to target
226
 
KEYEXCHANGE    = False           # Do key exchange-based auth?
227
 
LOGFILE        = "/dev/null"     # Where paramiko logging output goes
228
 
PROMPTUSERNAME = False           # Don't use $USER, prompt for username
229
 
PWORD          = ""              # Password
230
 
REDIRSTDERR    = False           # Redirect stderr to stdout
231
 
REPORTERR      = True            # Report stderr output from remote host
232
 
SSHCFGFILE     = "~/.ssh/config" # Default ssh configuration file location
233
 
TESTMODE       = True            # Run program in test mode, don't actually execute commands
234
 
TIMEOUT        = 15              # Connection attempt timeout (sec)
235
 
UNAME          = ""              # Login name
236
 
WRITEINVENTORY = False           # Write list of selected hosts to stdout as a single line
237
 

	
238
 

	
239
 
# Noise levels
240
 

	
241
 
NOISELEVEL     = 0            # Normal noisy
242
 
SILENT         = 1            # No program noise at all
243
 
QUIET          = 2            # Make output less noisy
244
 
NOISY          = 3            # Print output with extra detail
245
 

	
246
 

	
247
 
#####
248
 
# Global Data Structures & Variables
249
 
#####
250
 

	
251
 
Commands          = []
252
 
FileIncludeStack  = []
253
 
Get_Transfer_List = collections.OrderedDict()
254
 
Put_Transfer_List = collections.OrderedDict()
255
 
SSH_Configuration = {}
256
 
GlobalSymbolTable       = {}
257
 

	
258
 

	
259
 
#####
260
 
# Functions
261
 
#####
262
 

	
263
 
#####
264
 
# Gets rid of comments and strips leading/trailing whitespace
265
 
#####
266
 

	
267
 
def ConditionLine(line):
268
 
    return line.split(COMMENT)[0].strip()
269
 

	
270
 
# End of 'ConditionLine()'
271
 

	
272
 

	
273
 
#####
274
 
# Return List Of All Files Found On A String Of Paths
275
 
####
276
 

	
277
 
def FindFilesOnPath(pathenv):
278
 

	
279
 
    plist = os.getenv(pathenv) or ''
280
 
    slist = plist.split(PATHDELIM)
281
 
    found = []
282
 
    if slist:
283
 
        for p in slist:
284
 
            if p:
285
 
                if not p.endswith(PATHSEP):
286
 
                    p += PATHSEP
287
 
                try:
288
 
                    found += [ p+x for x in os.listdir(p)]
289
 

	
290
 
                except:
291
 
                    if NOISELEVEL not in (QUIET, SILENT):
292
 
                        PrintStderr(iNOPATH % p)
293
 

	
294
 
    return found
295
 

	
296
 
# End of 'FindFilesOnPath()'
297
 

	
298
 

	
299
 
####
300
 
# Get An Execution Variable Value
301
 
###
302
 

	
303
 
def GetExecutionVariable(cmd):
304
 

	
305
 

	
306
 
    try:
307
 
        origcmd = cmd
308
 
        status, cmd = commands.getstatusoutput(cmd.strip())
309
 

	
310
 
        # Blow out if the command failed
311
 

	
312
 
        if status:
313
 
            raise
314
 

	
315
 
    except:
316
 

	
317
 
        PrintReport([PROGNAME, eBADEXEC % origcmd], HANDLER=PrintStderr)
318
 
        ErrorExit("")
319
 

	
320
 
    # Return successfully
321
 
    return cmd
322
 

	
323
 
# End of 'GetExecutionVariable()'
324
 

	
325
 

	
326
 
#####
327
 
# Check To See If A Key Exists In A String, Excluding Quoted Substrings
328
 
#####
329
 

	
330
 
def KeyInString(key, string):
331
 

	
332
 
    """ Look for 'key' in 'string', but exclude segments of the string
333
 
        that are contained within single- or double quotes.
334
 
    """
335
 

	
336
 
    quote_chars = ('"', "'")
337
 

	
338
 
    InLiteral = False
339
 
    search = ""
340
 
    index = 0
341
 
    while index < len(string):
342
 

	
343
 
        char = string[index]
344
 

	
345
 
        if InLiteral:
346
 
            if char == quote_char:
347
 
                InLiteral = False
348
 

	
349
 
        elif char in quote_chars:
350
 
            quote_char = char
351
 
            InLiteral = True
352
 

	
353
 
        else:
354
 
            search += char
355
 

	
356
 
        index += 1
357
 

	
358
 
    status = False
359
 
    if search.count(key) > 0:
360
 
        status = True
361
 

	
362
 
    return status
363
 

	
364
 
# End of KeyInString
365
 

	
366
 

	
367
 
#####
368
 
# Print Message(s) To stderr
369
 
#####
370
 

	
371
 
def PrintStderr(msg, EOL="\n"):
372
 

	
373
 
    # If we've been told to redirect to stdout, do so instead
374
 

	
375
 
    if REDIRSTDERR:
376
 
        PrintStdout(msg, EOL)
377
 

	
378
 
    else:
379
 

	
380
 
        sys.stderr.write(msg + EOL)
381
 
        sys.stderr.flush()
382
 

	
383
 
# End of 'PrintStderr()'
384
 

	
385
 

	
386
 
#####
387
 
# Print Message(s) To stdout
388
 
#####
389
 

	
390
 
def PrintStdout(msg, EOL="\n"):
391
 
    sys.stdout.write(msg + EOL)
392
 
    sys.stdout.flush()
393
 

	
394
 
# End of 'PrintStdout()'
395
 

	
396
 

	
397
 
#####
398
 
# Display An Error Message And Exit
399
 
#####
400
 

	
401
 
def ErrorExit(msg):
402
 

	
403
 
    if msg:
404
 
        PrintStderr(msg)
405
 

	
406
 
    # If requested, print banner
407
 

	
408
 
    if BANNERSON:
409
 

	
410
 
        PrintStdout(BANNERMSG % (BANNERSTART, time.strftime("%Y-%m-%d"), time.strftime("%H:%M:%S")))
411
 
        PrintStdout(BANNERTIME % float(time.time() - StartTime))
412
 

	
413
 

	
414
 
    os._exit(1)
415
 

	
416
 
# End Of 'ErrorExit()'
417
 

	
418
 

	
419
 
#####
420
 
# Transfer Files To A Host
421
 
#####
422
 

	
423
 
def HostFileTransfer(host, user, pw, filelist, GET=False):
424
 

	
425
 
    # Get an sftp connection and move files
426
 
    try:
427
 
        ssh = SSH_Connect(host, user, pw, TIMEOUT)
428
 
        sftp = ssh.open_sftp()
429
 

	
430
 
        for src in filelist:
431
 

	
432
 
            # Process any .define substitions
433
 

	
434
 
            srcfile = VarSub(src)
435
 

	
436
 
            for destdir in filelist[src]:
437
 

	
438
 
                # Process any .define substitutions
439
 

	
440
 
                destdir = VarSub(destdir)
441
 

	
442
 
                # Make sure we have a trailing path separator
443
 

	
444
 
                destination = destdir
445
 
                if destination[-1] != PATHSEP:
446
 
                    destination += PATHSEP
447
 

	
448
 
                try:
449
 

	
450
 
                    if GET:
451
 
                        destination += host + HOSTSEP + os.path.basename(srcfile)
452
 
                        if NOISELEVEL != SILENT:
453
 
                            PrintStdout(iTXFILE %  (host + ":" + srcfile, destination))
454
 
                        sftp.get(srcfile, destination)
455
 
                        os.chmod(destination, sftp.stat(srcfile).st_mode)
456
 

	
457
 
                    else:
458
 
                        destination += os.path.basename(srcfile)
459
 
                        if NOISELEVEL != SILENT:
460
 
                            PrintStdout(iTXFILE %  (srcfile, host + ":" + destination))
461
 
                        sftp.put(srcfile, destination)
462
 
                        sftp.chmod(destination, os.stat(srcfile).st_mode)
463
 

	
464
 
                except:
465
 

	
466
 
                    PrintReport([host, eFXERROR % str(sys.exc_info()[1])], HANDLER=PrintStderr)
467
 

	
468
 
                    # Do we continue after failed file transfers or not?
469
 
                    if ABORTONFXERROR:
470
 
                        try:
471
 
                            sftp.close()
472
 
                            ssh.close()
473
 

	
474
 
                        except:
475
 
                            pass
476
 

	
477
 
                        ErrorExit("")
478
 

	
479
 
        sftp.close()
480
 
        ssh.close()
481
 

	
482
 
    except:
483
 

	
484
 
        PrintReport([host, eFXERROR % str(sys.exc_info()[1])], HANDLER=PrintStderr)
485
 

	
486
 
        # Do we continue after failed connection attempts or not?
487
 
        if ABORTONFXERROR:
488
 
            try:
489
 
                sftp.close()
490
 
                ssh.close()
491
 

	
492
 
            except:
493
 
                pass
494
 

	
495
 
            ErrorExit("")
496
 

	
497
 
# End of 'HostFileTransfer()'
498
 

	
499
 

	
500
 
def HostCommands(host, user, pw, sudopw, commands):
501
 

	
502
 
    # Figure out if we want report formatting
503
 

	
504
 
    Format = True
505
 
    if NOISELEVEL == SILENT:
506
 
        Format = False
507
 

	
508
 
    # Connect and run the command, reporting results as we go
509
 
    try:
510
 
        ssh = SSH_Connect(host, user, pw, TIMEOUT)
511
 
        if NOISELEVEL !=  QUIET:
512
 
            PrintReport([host, CONSUCCESS], FORMAT=Format)
513
 

	
514
 
        # Run all requested commands
515
 

	
516
 
        for command in commands:
517
 

	
518
 
            # Dereference variables
519
 

	
520
 
            command = VarSub(command)
521
 

	
522
 
            # It's possible to get blank lines from stdin.
523
 
            # Ignore them.
524
 

	
525
 
            if not command:
526
 
                continue
527
 

	
528
 
            # Process notifications - disabled in SILENT mode
529
 

	
530
 
            if command.startswith(NOTIFY):
531
 
                if NOISELEVEL != SILENT:
532
 
                    PrintStdout("%s> %s\n" % (NOTIFICATION, " ".join(command.split(NOTIFY)).strip()))
533
 
                continue
534
 

	
535
 
            # If this is a sudo run, force password to be read
536
 
            # from stdin thereby avoiding fiddling around with ptys.
537
 

	
538
 
            if KeyInString(SUDO + " ", command):
539
 
                command = command.replace(SUDO, "%s %s" % (SUDO, SUDOARGS), 1)
540
 

	
541
 
            stdin, stdout, stderr = ssh.exec_command(command)
542
 

	
543
 
            # If doing a sudo command, send the password
544
 

	
545
 
            if KeyInString(SUDO + " ", command):
546
 
                stdin.write("%s\n" % sudopw)
547
 
                stdin.flush()
548
 

	
549
 
                # If all we see on stderr at this point is our original
550
 
                # prompt, then then the sudo promotion worked.  A bad
551
 
                # password or bad command will generate additional noise
552
 
                # from sudo telling us to try again or that there was a
553
 
                # command error.
554
 

	
555
 
                sudonoise = " ".join(stderr.readline().split(SUDOPROMPT)).strip()
556
 

	
557
 
                if sudonoise:                 # sudo had problems
558
 

	
559
 
                    PrintReport([host + " [%s]" % command, eCMDFAILURE % (eBADSUDO % sudonoise)] + ["\n"], HANDLER=PrintStderr)
560
 

	
561
 
                    # Abort program on sudo failure.  This is default behavior
562
 
                    # but can be overriden on the command line.
563
 

	
564
 
                    if ABORTBADSUDO:
565
 
                        ssh.close()
566
 
                        raise SystemExit
567
 
                    else:
568
 
                        break
569
 

	
570
 
            cmdreport = " [%s]" % command
571
 
            if NOISELEVEL == QUIET:
572
 
                cmdreport = ""
573
 

	
574
 
            PrintReport([host + " (stdout)" + cmdreport, "\n"] + stdout.readlines() + ["\n"], FORMAT=Format)
575
 

	
576
 
            if REPORTERR:
577
 
                PrintReport([host + " (stderr)" + cmdreport, "\n"] + stderr.readlines() + ["\n"], HANDLER=PrintStderr, FORMAT=Format)
578
 

	
579
 
    # Handle aborts
580
 

	
581
 
    except SystemExit:
582
 
        ErrorExit(ABORTING)
583
 

	
584
 
    # Catch authentication problems explicitly
585
 

	
586
 
    except paramiko.AuthenticationException:
587
 
        PrintReport([host, eCMDFAILURE % eNOLOGIN], HANDLER=PrintStderr)
588
 

	
589
 
    # Everything else is some kind of connection problem
590
 

	
591
 
    except:
592
 
        PrintReport([host, eCMDFAILURE % (eNOCONNECT % str(sys.exc_info()[1]))], HANDLER=PrintStderr)
593
 

	
594
 
    # Close any remaining ssh connection
595
 
    try:
596
 
        ssh.close()
597
 
    except:
598
 
        pass
599
 

	
600
 
# End of 'HostCommands()'
601
 

	
602
 

	
603
 
#####
604
 
# Print Report
605
 
#####
606
 

	
607
 
# Expects input as [host, success/failure message, result1, result2, ...]
608
 
# Uses print handler to stdout by default but can be overriden at call
609
 
# time to invoke any arbitrary handler function.
610
 

	
611
 
def PrintReport(results, HANDLER=PrintStdout, FORMAT=True):
612
 

	
613
 

	
614
 
    if FORMAT:
615
 

	
616
 
        hostname = results[0]
617
 
        HANDLER(SEPARATOR + hostname +
618
 
                TRAILER +
619
 
                (PADWIDTH - len(results[0])) * " " +
620
 
                results[1])
621
 

	
622
 
        # Prepend the host name if we've asked for noisy reporting
623
 

	
624
 
        hostnoise =""
625
 
        if NOISELEVEL == NOISY:
626
 
            hostnoise = HOSTNOISE % hostname
627
 

	
628
 
        for r in results[2:]:                             # Command Results
629
 
            HANDLER(hostnoise + INDENTWIDTH * " " + r.strip())
630
 

	
631
 
    # In (silent) unformatted mode we just return results with no
632
 
    # headers or formatting.  We suppress the last line in the list.
633
 
    # It is an empty line introduced for formatting by the caller, but
634
 
    # isn't really part of the returned output.
635
 
    else:
636
 

	
637
 
        for r in results[2:-1]:
638
 
            HANDLER(r, EOL="")
639
 

	
640
 
# End of 'PrintReport()'
641
 

	
642
 

	
643
 
#####
644
 
# Process A File Transfer Request
645
 
#####
646
 

	
647
 
def ProcessTXRQ(request, storage):
648
 

	
649
 
    src_dest = request.split()
650
 
    if len(src_dest) != 2:
651
 
        ErrorExit(eBADTXRQ % src_dest)
652
 

	
653
 
    else:
654
 

	
655
 
        if src_dest[0] not in storage:
656
 
            storage[src_dest[0]] = [src_dest[1],]
657
 

	
658
 
        else:
659
 
            storage[src_dest[0]].append(src_dest[1])
660
 

	
661
 
# End of 'ProcessTXRQ'
662
 

	
663
 

	
664
 
#####
665
 
# Read File Handling Comments And Directives
666
 
#####
667
 

	
668
 
def ReadFile(fname, envvar, listcontainer, containingfile=""):
669
 

	
670
 
    LocalSymbolTable = {}
671
 

	
672
 
    # Check to see if we can find the file, searching the
673
 
    # the relevant include environment variable path, if any
674
 

	
675
 
    filename = SearchPath(fname, envvar)
676
 
    if not filename:
677
 
        ErrorExit(eBADFILE % fname)
678
 

	
679
 
    # Make sure we don't have a cyclic include reference
680
 

	
681
 
    if filename in FileIncludeStack:
682
 
        ErrorExit(eINCLUDECYCLE % containingfile + SEPARATOR + filename)
683
 

	
684
 
    else:
685
 
        FileIncludeStack.append(filename)  # Push it on to the stack history
686
 

	
687
 
    # Line parsing starts here
688
 
    try:
689
 

	
690
 
        f = open(filename)
691
 
        for line in f.readlines():
692
 

	
693
 
            # Cleanup comments and whitespace
694
 

	
695
 
            line = ConditionLine(line)
696
 

	
697
 
            # Process file transfer requests
698
 

	
699
 
            if line.startswith(FILEGET):
700
 
                val = line.split(FILEGET)[1].strip()
701
 
                ProcessTXRQ(val, Get_Transfer_List)
702
 

	
703
 
            elif line.startswith(FILEPUT):
704
 
                val = line.split(FILEPUT)[1].strip()
705
 
                ProcessTXRQ(val, Put_Transfer_List)
706
 

	
707
 
            # Process local variable definitions
708
 

	
709
 
            elif line.startswith(LOCAL):
710
 

	
711
 
                line = line.split(LOCAL)[1]
712
 
                if line.count(ASSIGN) == 0:
713
 
                    ErrorExit(eBADDEFINE % line)
714
 

	
715
 
                else:
716
 

	
717
 
                    name = line.split(ASSIGN)[0].strip()
718
 
                    val  = "=".join(line.split(ASSIGN)[1:]).strip()
719
 

	
720
 
                    if name:
721
 

	
722
 
                        # Process references to execution variables
723
 

	
724
 
                        if val.startswith(EXECUTE):
725
 
                            val  = GetExecutionVariable(val[1:])
726
 

	
727
 
                        LocalSymbolTable[name] = val
728
 

	
729
 
                    else:
730
 
                        ErrorExit(eBADDEFINE % line)
731
 

	
732
 
            # Process global variable definitions
733
 

	
734
 
            elif line.startswith(DEFINE):
735
 

	
736
 
                line = line.split(DEFINE)[1]
737
 
                if line.count(ASSIGN) == 0:
738
 
                    ErrorExit(eBADDEFINE % line)
739
 

	
740
 
                else:
741
 

	
742
 
                    name = line.split(ASSIGN)[0].strip()
743
 
                    val  = "=".join(line.split(ASSIGN)[1:]).strip()
744
 

	
745
 
                    if name:
746
 

	
747
 
                        # Process references to execution variables
748
 

	
749
 
                        if val.startswith(EXECUTE):
750
 
                            val  = GetExecutionVariable(val[1:])
751
 

	
752
 

	
753
 
                        GlobalSymbolTable[name] = val
754
 

	
755
 
                    else:
756
 
                        ErrorExit(eBADDEFINE % line)
757
 

	
758
 
            # Process file includes
759
 
            elif line:
760
 
                if line.startswith(INCLUDE):
761
 
                    fname = VarSub(ConditionLine(line.split(INCLUDE)[1]))
762
 
                    ReadFile(fname, envvar, listcontainer, containingfile=filename)
763
 

	
764
 
                # It's a normal line - do local variable substitution and save
765
 
                else:
766
 
                    listcontainer.append(VarSub(line, Table=LocalSymbolTable))
767
 
        f.close()
768
 

	
769
 
        FileIncludeStack.pop()   # Remove this invocation from the stack
770
 
        return listcontainer
771
 

	
772
 
    except:
773
 
        ErrorExit(eBADFILE % filename)
774
 

	
775
 
# End of 'ReadFile()'
776
 

	
777
 

	
778
 
#####
779
 
# Setup An ssh Connection
780
 
#####
781
 

	
782
 
def SSH_Connect(host, user, pw, time):
783
 

	
784
 
    ssh = paramiko.SSHClient()
785
 
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
786
 

	
787
 
    keyfiles = None
788
 
    if host in SSH_Configuration:
789
 
        entry  = SSH_Configuration[host]
790
 
        keyfiles, host = entry[0], entry[1]
791
 

	
792
 
    if KEYEXCHANGE:
793
 
        ssh.connect(host, username=user, key_filename=keyfiles, timeout=time)
794
 
    else:
795
 
        ssh.connect(host, username=user, password=pw, allow_agent=False, look_for_keys=False, timeout=time)
796
 

	
797
 
    return ssh
798
 

	
799
 
# End of 'SSH_Connect()'
800
 

	
801
 

	
802
 
#####
803
 
# Read Any Local SSH Configuration
804
 
#####
805
 

	
806
 
def SSH_GetConfig(filename):
807
 

	
808
 
   # NOTE: We only support the "IdentityFile" and "HostName"
809
 
   # configuration directives here.
810
 

	
811
 
    retval = {}
812
 
    sshconfig = paramiko.SSHConfig()
813
 
    try:
814
 
        f = open(os.path.expanduser(filename))
815
 

	
816
 
    # File open failed, but we go on anyway
817
 
    except:
818
 
        PrintStderr(iNOCFGFILE % filename)
819
 
        return retval
820
 

	
821
 
    sshconfig.parse(f)
822
 
    f.close()
823
 

	
824
 
    # Return local configuration as a dictonary with hostnames as keys
825
 

	
826
 
    for host in sshconfig.get_hostnames():
827
 
        if host != "*":
828
 
            cfgentry = sshconfig.lookup(host)
829
 
            output = []
830
 

	
831
 
            keyfiles = []
832
 
            if CFGKEYID in cfgentry:
833
 
                keyfiles = cfgentry[CFGKEYID]
834
 

	
835
 
            realhost = host
836
 
            if CFGREALHOST in cfgentry:
837
 
                realhost = cfgentry[CFGREALHOST]
838
 

	
839
 
            retval[host] = [keyfiles, realhost]
840
 

	
841
 
    return retval
842
 

	
843
 
# End of 'SSH_GetConfig()'
844
 

	
845
 

	
846
 
#####
847
 
# Search A Path For A File, Returning First Match
848
 
#####
849
 

	
850
 
def SearchPath(filename, pathlist, delimiter=PATHDELIM):
851
 

	
852
 
    # What we'll return if we find nothing
853
 
    retval = ""
854
 

	
855
 
    # Handle fully qualified filenames
856
 
    # But ignore this, if its a directory with a matching name
857
 

	
858
 
    if os.path.exists(filename) and os.path.isfile(filename):
859
 
        retval =  os.path.realpath(filename)
860
 

	
861
 
    # Find first instance along specified path if one has been specified
862
 
    elif pathlist:
863
 

	
864
 
        paths = pathlist.split(delimiter)
865
 
        for path in paths:
866
 

	
867
 
            if path and path[-1] != PATHSEP:
868
 
                path += PATHSEP
869
 

	
870
 
                path += filename
871
 

	
872
 
                if os.path.exists(path):
873
 
                    retval = os.path.realpath(path)
874
 
                    break
875
 
    return retval
876
 

	
877
 
# End of 'SearchPath()'
878
 

	
879
 

	
880
 
#####
881
 
# Do Variable Substitution In A String
882
 
#####
883
 

	
884
 
def VarSub(line, Table=GlobalSymbolTable):
885
 

	
886
 
    for symbol in Table:
887
 
        line = line.replace(symbol, Table[symbol])
888
 

	
889
 
    return line
890
 

	
891
 
# End of 'VarSub()'
892
 

	
893
 

	
894
 
# ---------------------- Program Entry Point ---------------------- #
895
 

	
896
 
#####
897
 
# Note starting time
898
 
#####
899
 

	
900
 
StartTime = time.time() # Need this here in case we error abort
901
 
                        # on command line processing.  Gets
902
 
                        # reset below for normal processing.
903
 

	
904
 
#####
905
 
# Process Any Options User Set In The Environment Or On Command Line
906
 
#####
907
 

	
908
 
# Handle any options set in the environment
909
 

	
910
 
OPTIONS = sys.argv[1:]
911
 
envopt = os.getenv(PROGENV)
912
 
if envopt:
913
 
    OPTIONS = shlex.split(envopt) + OPTIONS
914
 

	
915
 
# Combine them with those given on the command line
916
 
# This allows the command line to override defaults
917
 
# set in the environment
918
 

	
919
 
try:
920
 
    opts, args = getopt.getopt(OPTIONS, OPTIONSLIST)
921
 

	
922
 
except getopt.GetoptError, (errmsg, badarg):
923
 
    ErrorExit(eBADARG % errmsg)
924
 

	
925
 
for opt, val in opts:
926
 

	
927
 
    if opt == "-B":
928
 
        BANNERSON = True
929
 

	
930
 
    if opt == "-C":
931
 
        SSHCFGFILE = val
932
 

	
933
 
    if opt == "-E":
934
 
        REDIRSTDERR = True
935
 

	
936
 
    if ( opt == "-F" or opt == "-V"):
937
 

	
938
 
        # Look for string matches or misses in
939
 
        # the host- and command files
940
 

	
941
 
        files = FindFilesOnPath(HOSTINCL)
942
 
        files += FindFilesOnPath(CMDINCL)
943
 

	
944
 
        for fn in files:
945
 
            f = open(fn, 'r')
946
 
            content = f.readlines()
947
 
            f.close()
948
 

	
949
 
            linenum = 0
950
 
            matched = False
951
 
            for line in content:
952
 

	
953
 
                linenum += 1
954
 
                for string in val.split():
955
 
                    if string.lower() in line.lower():
956
 
                        if opt == "-F":
957
 
                            PrintStdout("%s:%s %s" % (fn, linenum, line.strip()))
958
 
                        matched = True
959
 
                        break
960
 

	
961
 
            # Handle non-matches if we asked for that
962
 

	
963
 
            if (not matched and opt == '-V'):
964
 
                PrintStdout("%s: %s" % (fn, NOMATCH))
965
 

	
966
 

	
967
 
        sys.exit()
968
 

	
969
 
    if opt == "-K":
970
 
        KEYEXCHANGE = False
971
 

	
972
 
    if opt == "-G":
973
 
        ProcessTXRQ(val, Get_Transfer_List)
974
 

	
975
 
    if opt == "-H":
976
 
        Hosts += val.split()
977
 

	
978
 
    if opt == "-L":
979
 

	
980
 
        # List all the host- and command files
981
 

	
982
 
        for hdr, pathenv in [[iHOSTFILES, HOSTINCL],
983
 
                             [iCMDFILES,   CMDINCL]]:
984
 
            PrintStdout(hdr)
985
 
            PrintStdout("\n".join(FindFilesOnPath(pathenv)), EOL="\n\n")
986
 

	
987
 
        sys.exit()
988
 

	
989
 
    if opt == "-N":
990
 
        PROMPTUSERNAME = True
991
 
        KEYEXCHANGE = False
992
 

	
993
 
    if opt == "-P":
994
 
        ProcessTXRQ(val, Put_Transfer_List)
995
 

	
996
 
    if opt == "-S":
997
 
        GETSUDOPW = True
998
 

	
999
 
    if opt == "-T":
1000
 
        try:
1001
 
            TIMEOUT = int(val)
1002
 
        except:
1003
 
            ErrorExit(eBADTIMEOUT)
1004
 

	
1005
 
    if opt == "-W":
1006
 
        WRITEINVENTORY = True
1007
 

	
1008
 
    if opt == "-a":
1009
 
        ABORTONFXERROR = False
1010
 

	
1011
 
    if opt == "-b":
1012
 
        ABORTBADSUDO = False
1013
 

	
1014
 
    if opt == "-e":
1015
 
        REPORTERR = False
1016
 

	
1017
 
    if opt == "-f":
1018
 
        Commands = ReadFile(val, os.getenv(CMDINCL), Commands)
1019
 

	
1020
 
    if opt == "-h":
1021
 
        PrintStdout(USAGE)
1022
 
        sys.exit()
1023
 

	
1024
 
    if opt == "-i":
1025
 
        for hostfile in val.split():
1026
 
            Hosts = ReadFile(hostfile, os.getenv(HOSTINCL), Hosts)
1027
 

	
1028
 
    if opt == "-k":
1029
 
        KEYEXCHANGE = True
1030
 

	
1031
 
    if opt == "-l":
1032
 
        LOGFILE = val
1033
 

	
1034
 
    if opt == "-n":
1035
 
        UNAME = val
1036
 

	
1037
 
    if opt == "-p":
1038
 
        PWORD = val
1039
 

	
1040
 
    if opt == "-q":
1041
 
        NOISELEVEL = QUIET
1042
 

	
1043
 
    if opt == "-r":
1044
 
        BANNERSON = False
1045
 

	
1046
 
    if opt == "-s":
1047
 
        NOISELEVEL = SILENT
1048
 

	
1049
 
    if opt == "-t":
1050
 
        TESTMODE = True
1051
 

	
1052
 
    if opt == "-v":
1053
 
        PrintStdout(GITID)
1054
 
        sys.exit()
1055
 

	
1056
 
    if opt == "-x":
1057
 
        TESTMODE = False
1058
 

	
1059
 
    if opt == "-y":
1060
 
        NOISELEVEL = NOISY
1061
 

	
1062
 

	
1063
 
#####
1064
 
# Intitialize paramiko Logging
1065
 
#####
1066
 

	
1067
 
paramiko.util.log_to_file(LOGFILE)
1068
 

	
1069
 

	
1070
 
#####
1071
 
# Command Line Command Definition Processing
1072
 
#####
1073
 

	
1074
 
# Must have a list of hosts or we cannot go on with any
1075
 
# operation whether file xfer or command execution
1076
 

	
1077
 
if not Hosts:
1078
 
    ErrorExit(eNOHOSTS)
1079
 

	
1080
 
else:
1081
 
    command = " ".join(args[0:])
1082
 

	
1083
 
# Put it in a list data structure because this is what the
1084
 
# HostCommands() function expects.  This is necessary to handle multi
1085
 
# command input from from a file.
1086
 

	
1087
 
command = ConditionLine(command)
1088
 

	
1089
 
if command:
1090
 

	
1091
 
    # Do variable substitution here like any other command
1092
 
    Commands.append(command)
1093
 

	
1094
 
#####
1095
 
# Authentication Credential Processing
1096
 
#####
1097
 

	
1098
 
# Precedence of authentication credential sources:
1099
 
#
1100
 
#     1) Key exchange
1101
 
#     2) Forced prompting for name via -N
1102
 
#     3) Command Line/$TSSHBATCH env variable sets name
1103
 
#     4) Name picked up from $USER  (Default behavior)
1104
 

	
1105
 

	
1106
 
# Regardless of authorization type, we need a user name.
1107
 
# Preset commandline and/or program option variable username takes
1108
 
# precedence
1109
 

	
1110
 
if not UNAME:
1111
 
    UNAME = os.getenv(USERVAR)
1112
 

	
1113
 
# By default, use the above as the login name and don't prompt for it
1114
 
# unless overriden on the command line with -N
1115
 

	
1116
 
if PROMPTUSERNAME:
1117
 

	
1118
 
    current_user = UNAME
1119
 
    UNAME = raw_input(pUSER %current_user)
1120
 
    if not UNAME:                 # User just hit return - wants default
1121
 
        UNAME = current_user
1122
 

	
1123
 
# For password auth, preset commandline and/or program option
1124
 
# variable password takes precedence
1125
 

	
1126
 
if not KEYEXCHANGE and not PWORD:
1127
 
    PWORD  = getpass.getpass(pPASS)
1128
 

	
1129
 
#####
1130
 
# If Needed, Get sudo Password
1131
 
####
1132
 

	
1133
 
# The need to prompt for a sudo password depends on a number of
1134
 
# conditions:
1135
 
#
1136
 
# If a login password is present either via manual entry or -p, sudo
1137
 
# will use that without further prompting.  (Default)
1138
 
#
1139
 
# The user is prompted for a sudo password under two conditions:
1140
 
#
1141
 
#  1) -k option was selected but no password was set with -p
1142
 
#  2) -S option was selected
1143
 
#
1144
 
# If the user IS prompted for a sudo password, any login password
1145
 
# previously entered - either via -p or interactive entry - will be
1146
 
# used as the default.  The user can hit enter to accept this or enter
1147
 
# a different password.  This allows login and sudo passwords to be
1148
 
# the same or different.
1149
 

	
1150
 
# Find out if we have any sudo commands
1151
 

	
1152
 
SUDOPRESENT = False
1153
 
for command in Commands:
1154
 
    if KeyInString(SUDO + " ", command):
1155
 
            SUDOPRESENT = True
1156
 

	
1157
 
# Check condition 1) above.
1158
 
# (Condition 2 handled during options processing).
1159
 

	
1160
 
if KEYEXCHANGE and not PWORD:
1161
 
    GETSUDOPW = True
1162
 

	
1163
 
SUDOPW = PWORD
1164
 
if SUDOPRESENT and GETSUDOPW:
1165
 

	
1166
 
    sudopwmsg   = pSUDO
1167
 
    if PWORD:
1168
 
        sudopwmsg = sudopwmsg[:-2] + " " + SUDOPWHINT
1169
 

	
1170
 
    SUDOPW = getpass.getpass(sudopwmsg)
1171
 
    if PWORD and not SUDOPW:
1172
 
        SUDOPW = PWORD
1173
 

	
1174
 

	
1175
 
# Report time statistics if requested
1176
 

	
1177
 
if BANNERSON:
1178
 
    StartTime= time.time()   # The the real starting time - the time the work began
1179
 
    PrintStdout(BANNERMSG % (BANNERSTART, time.strftime("%Y-%m-%d"), time.strftime("%H:%M:%S")))
1180
 

	
1181
 
#####
1182
 
# Do The Requested Work
1183
 
#####
1184
 

	
1185
 
# If we're running testmode, just report the final list of
1186
 
# hosts and commands that would be run
1187
 

	
1188
 
if TESTMODE:
1189
 

	
1190
 
    symtbl = []
1191
 
    gets   = []
1192
 
    puts   = []
1193
 

	
1194
 
    # Unroll and format dictionary structures
1195
 

	
1196
 
    symbols = GlobalSymbolTable.keys()
1197
 
    symbols.sort()
1198
 
    for symbol in symbols:
1199
 
        symtbl.append(symbol + (PADWIDTH - len(symbol)) * " "+ SEPARATOR + GlobalSymbolTable[symbol])
1200
 

	
1201
 
    for xfers, unrolled in ((Get_Transfer_List, gets), (Put_Transfer_List, puts)):
1202
 

	
1203
 
        for source in xfers:
1204
 
            for dest in xfers[source]:
1205
 

	
1206
 
                # Dereference any variables in the file transfer specification
1207
 

	
1208
 
                source = VarSub(source)
1209
 
                dest   = VarSub(dest)
1210
 

	
1211
 
                unrolled.append(source + (PADWIDTH*3 - len(source)) * " "+ SEPARATOR + dest)
1212
 

	
1213
 
    # Dereference any variables in the list of commands
1214
 

	
1215
 
    index = 0
1216
 
    while (index < len(Commands)):
1217
 

	
1218
 
        Commands[index] = VarSub(Commands[index])
1219
 
        index += 1
1220
 

	
1221
 
    # Dereference any variables in the list of hosts
1222
 

	
1223
 
    index = 0
1224
 
    while (index < len(Hosts)):
1225
 

	
1226
 
        Hosts[index] = VarSub(Hosts[index])
1227
 
        index += 1
1228
 

	
1229
 
    # Write the inventory out if that's all that was asked for
1230
 

	
1231
 
    if WRITEINVENTORY:
1232
 
        PrintStdout(" ".join(Hosts))
1233
 

	
1234
 
    # Otherwise, print the summary
1235
 

	
1236
 
    else:
1237
 
        for prompt, description, items in ((TESTRUN,  " ".join(OPTIONS), ["\n"]),
1238
 
                                           (SYMTABLE, "",                 symtbl + ["\n"]),
1239
 
                                           (HOSTLIST, "",                  Hosts + ["\n"]),
1240
 
                                           (GETFILES, "",                   gets + ["\n"]),
1241
 
                                           (PUTFILES, "",                   puts + ["\n"]),
1242
 
                                           (COMMANDS, "",               Commands + ["\n"])
1243
 
                                          ):
1244
 

	
1245
 
            PrintReport([prompt, description] + items)
1246
 

	
1247
 
# Otherwise, actually do the work by iterating over the list of hosts,
1248
 
# executing any file transfers and commands.  Accomodate commenting
1249
 
# out hosts in a list.
1250
 

	
1251
 
else :
1252
 

	
1253
 
    # Pick up any local configuration data
1254
 

	
1255
 
    SSH_Configuration = SSH_GetConfig(SSHCFGFILE)
1256
 

	
1257
 
    # Check to see if user is trying to override any builtins
1258
 

	
1259
 
    protected = []
1260
 
    for builtin in BuiltIns:
1261
 
        if builtin in GlobalSymbolTable:
1262
 
            protected.append(builtin)
1263
 

	
1264
 
    # Now iterate over requested hosts
1265
 

	
1266
 
    hostnum = 0
1267
 
    for host in Hosts:
1268
 

	
1269
 
        # Update the host counter
1270
 

	
1271
 
        hostnum += 1
1272
 

	
1273
 
        # Add internally generated symbols to the symbol table.
1274
 
        # That way, both user-defined and builtin symbols will
1275
 
        # subsequently be substituted.
1276
 

	
1277
 
        # Find out the effective name of the user doing this
1278
 

	
1279
 
        uname = UNAME
1280
 
        if not uname:
1281
 
            uname = os.getenv(USERVAR)
1282
 

	
1283
 
        internals = [
1284
 
                     (DATE, time.strftime("%Y%m%d")),
1285
 
                     (DATETIME, time.strftime("%Y%m%d%H%M%S")),
1286
 
                     (HOSTNAME, host),
1287
 
                     (HOSTNUM, str(hostnum)),
1288
 
                     (HOSTSHORT, host.split('.')[0]),
1289
 
                     (LOGINNAME, uname),
1290
 
                     (TIME, time.strftime("%H%M%S")),
1291
 
                    ]
1292
 

	
1293
 
        # Install builtins in the symbol table but only if the
1294
 
        # user isn't overriding them.
1295
 

	
1296
 
        for symbol, value in internals:
1297
 
            if symbol not in protected:
1298
 
                GlobalSymbolTable[symbol] = value
1299
 

	
1300
 
        # Apply any relevant variable dereferencing
1301
 

	
1302
 
        host = VarSub(host)
1303
 

	
1304
 
        if Get_Transfer_List:
1305
 
            HostFileTransfer(host, UNAME, PWORD, Get_Transfer_List, GET=True)
1306
 

	
1307
 
        if Put_Transfer_List:
1308
 
            HostFileTransfer(host, UNAME, PWORD, Put_Transfer_List, GET=False)
1309
 

	
1310
 
        if Commands:
1311
 
            HostCommands(host, UNAME, PWORD, SUDOPW, Commands)
1312
 

	
1313
 
#####
1314
 
# If requested, print startup banner
1315
 
#####
1316
 

	
1317
 
if BANNERSON:
1318
 

	
1319
 
    PrintStdout(BANNERMSG % (BANNEREND, time.strftime("%Y-%m-%d"), time.strftime("%H:%M:%S")))
1320
 
    PrintStdout(BANNERTIME % float(time.time() - StartTime))
0 comments (0 inline, 0 general)