2022-03-19 20:41:35 -03:00
#!/usr/bin/env python3
2022-03-17 19:05:23 -03:00
#Imports
import os
import re
import pexpect
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
import ast
from time import sleep
import datetime
import sys
2022-03-30 19:51:54 -03:00
import threading
2022-03-17 19:05:23 -03:00
2022-03-19 20:41:35 -03:00
#functions and classes
2022-03-17 19:05:23 -03:00
class node :
2022-03-18 15:32:48 -03:00
def __init__ ( self , unique , host , options = '' , logs = '' , password = '' , port = '' , protocol = '' , type = '' , user = '' , config = '' ):
if config == '' :
2022-03-17 19:05:23 -03:00
self . idletime = 0
2022-03-18 15:32:48 -03:00
self . key = None
else :
self . idletime = config . config [ "idletime" ]
self . key = config . key
2022-03-17 19:05:23 -03:00
self . unique = unique
self . id = self . unique . split ( "@" )[ 0 ]
attr = { "host" : host , "logs" : logs , "options" : options , "port" : port , "protocol" : protocol , "user" : user }
for key in attr :
profile = re . search ( "^@(.*)" , attr [ key ])
2022-03-18 15:32:48 -03:00
if profile and config != '' :
2022-03-17 19:05:23 -03:00
setattr ( self , key , config . profiles [ profile . group ( 1 )][ key ])
elif attr [ key ] == '' and key == "protocol" :
try :
setattr ( self , key , config . profiles [ "default" ][ key ])
except :
setattr ( self , key , "ssh" )
else :
setattr ( self , key , attr [ key ])
if isinstance ( password , list ):
self . password = []
for i , s in enumerate ( password ):
profile = re . search ( "^@(.*)" , password [ i ])
2022-03-18 15:32:48 -03:00
if profile and config != '' :
2022-03-17 19:05:23 -03:00
self . password . append ( config . profiles [ profile . group ( 1 )][ "password" ])
else :
2022-03-18 15:32:48 -03:00
self . password = [ password ]
2022-03-17 19:05:23 -03:00
def __passtx ( self , passwords , * , keyfile = None ):
dpass = []
2022-03-18 15:32:48 -03:00
if keyfile is None :
keyfile = self . key
2022-03-25 12:25:59 -03:00
if keyfile is not None :
2022-03-18 15:32:48 -03:00
key = RSA . import_key ( open ( keyfile ) . read ())
decryptor = PKCS1_OAEP . new ( key )
2022-03-17 19:05:23 -03:00
for passwd in passwords :
2022-03-29 18:57:27 -03:00
if not re . match ( '^b[ \"\' ].+[ \"\' ]$' , passwd ):
2022-03-18 15:32:48 -03:00
dpass . append ( passwd )
else :
try :
2022-03-29 18:57:27 -03:00
decrypted = decryptor . decrypt ( ast . literal_eval ( passwd )) . decode ( "utf-8" )
2022-03-18 15:32:48 -03:00
dpass . append ( decrypted )
except :
2022-03-25 12:25:59 -03:00
raise ValueError ( "Missing or corrupted key" )
2022-03-17 19:05:23 -03:00
return dpass
def _logfile ( self , logfile = None ):
if logfile == None :
logfile = self . logs
logfile = logfile . replace ( "$ {id} " , self . id )
logfile = logfile . replace ( "$ {unique} " , self . unique )
logfile = logfile . replace ( "$ {host} " , self . host )
logfile = logfile . replace ( "$ {port} " , self . port )
logfile = logfile . replace ( "$ {user} " , self . user )
logfile = logfile . replace ( "$ {protocol} " , self . protocol )
now = datetime . datetime . now ()
dateconf = re . search ( r '\$\{date \' (.*) \' }' , logfile )
if dateconf :
logfile = re . sub ( r '\$\{date (.*)}' , now . strftime ( dateconf . group ( 1 )), logfile )
return logfile
2022-03-18 16:16:31 -03:00
def _logclean ( self , logfile , var = False ):
if var == False :
t = open ( logfile , "r" ) . read ()
else :
t = logfile
t = t . replace ( " \n " , "" , 1 ) . replace ( " \a " , "" )
2022-03-17 19:05:23 -03:00
t = t . replace ( ' \n\n ' , ' \n ' )
t = re . sub ( '.\[K' , '' , t )
while True :
tb = re . sub ( '. \b ' , '' , t , count = 1 )
if len ( t ) == len ( tb ):
break
t = tb
2022-03-18 15:32:48 -03:00
ansi_escape = re . compile ( r '\x1B(?:[@-Z \\ -_]|\[[0-?]*[ -/ ]*[@-~])' )
t = ansi_escape . sub ( '' , t )
2022-03-18 16:16:31 -03:00
if var == False :
d = open ( logfile , "w" )
d . write ( t )
d . close ()
return
else :
return t
2022-03-17 19:05:23 -03:00
2022-03-22 19:54:05 -03:00
def interact ( self , debug = False ):
connect = self . _connect ( debug = debug )
2022-03-18 15:32:48 -03:00
if connect == True :
2022-03-30 17:36:27 -03:00
size = re . search ( 'columns=([0-9]+).*lines=([0-9]+)' , str ( os . get_terminal_size ()))
self . child . setwinsize ( int ( size . group ( 2 )), int ( size . group ( 1 )))
2022-03-18 15:32:48 -03:00
print ( "Connected to " + self . unique + " at " + self . host + ( ":" if self . port != '' else '' ) + self . port + " via: " + self . protocol )
2022-03-25 12:25:59 -03:00
if 'logfile' in dir ( self ):
2022-03-18 15:32:48 -03:00
self . child . logfile_read = open ( self . logfile , "wb" )
2022-03-25 12:25:59 -03:00
elif debug :
self . child . logfile_read = None
2022-03-18 15:32:48 -03:00
if 'missingtext' in dir ( self ):
2022-03-19 20:41:35 -03:00
print ( self . child . after . decode (), end = '' )
2022-03-18 15:32:48 -03:00
self . child . interact ()
2022-03-22 19:54:05 -03:00
if "logfile" in dir ( self ) and not debug :
2022-03-18 15:32:48 -03:00
self . _logclean ( self . logfile )
2022-03-25 12:25:59 -03:00
else :
print ( connect )
2022-03-25 17:55:43 -03:00
exit ( 1 )
2022-03-18 15:32:48 -03:00
2022-03-28 10:20:00 -03:00
def run ( self , commands , * , folder = '' , prompt = '>$|#$|\$$|>.$|#.$|\$.$' , stdout = False ):
2022-03-18 15:32:48 -03:00
connect = self . _connect ()
if connect == True :
2022-03-30 17:36:27 -03:00
expects = [ prompt , pexpect . EOF ]
2022-03-18 15:32:48 -03:00
output = ''
if isinstance ( commands , list ):
for c in commands :
2022-03-30 17:36:27 -03:00
result = self . child . expect ( expects )
2022-03-18 15:32:48 -03:00
self . child . sendline ( c )
2022-03-30 17:36:27 -03:00
match result :
case 0 :
output = output + self . child . before . decode () + self . child . after . decode ()
case 1 :
output = output + self . child . before . decode ()
2022-03-18 15:32:48 -03:00
else :
2022-03-30 17:36:27 -03:00
result = self . child . expect ( expects )
2022-03-18 16:16:31 -03:00
self . child . sendline ( commands )
2022-03-30 17:36:27 -03:00
match result :
case 0 :
output = output + self . child . before . decode () + self . child . after . decode ()
case 1 :
output = output + self . child . before . decode ()
result = self . child . expect ( expects )
match result :
case 0 :
output = output + self . child . before . decode () + self . child . after . decode ()
case 1 :
output = output + self . child . before . decode ()
2022-03-28 10:20:00 -03:00
self . child . close ()
2022-03-30 17:36:27 -03:00
output = output . lstrip ()
2022-03-25 12:25:59 -03:00
if stdout == True :
print ( output )
if folder != '' :
2022-03-18 15:32:48 -03:00
with open ( folder + "/" + self . unique , "w" ) as f :
f . write ( output )
f . close ()
self . _logclean ( folder + "/" + self . unique )
2022-03-18 16:16:31 -03:00
self . output = output
return output
2022-03-25 17:55:43 -03:00
else :
2022-03-30 17:36:27 -03:00
self . output = connect
2022-03-25 17:55:43 -03:00
return connect
2022-03-18 15:32:48 -03:00
2022-03-28 10:20:00 -03:00
def test ( self , commands , expected , * , prompt = '>$|#$|\$$|>.$|#.$|\$.$' ):
connect = self . _connect ()
if connect == True :
2022-03-30 17:36:27 -03:00
expects = [ prompt , pexpect . EOF ]
2022-03-28 10:20:00 -03:00
output = ''
if isinstance ( commands , list ):
for c in commands :
2022-03-30 17:36:27 -03:00
result = self . child . expect ( expects )
2022-03-28 10:20:00 -03:00
self . child . sendline ( c )
2022-03-30 17:36:27 -03:00
match result :
case 0 :
output = output + self . child . before . decode () + self . child . after . decode ()
case 1 :
output = output + self . child . before . decode ()
2022-03-28 10:20:00 -03:00
else :
2022-03-30 17:36:27 -03:00
self . child . expect ( expects )
2022-03-28 10:20:00 -03:00
self . child . sendline ( commands )
output = output + self . child . before . decode () + self . child . after . decode ()
2022-03-30 17:36:27 -03:00
expects = [ expected , prompt , pexpect . EOF ]
2022-03-28 10:20:00 -03:00
results = self . child . expect ( expects )
match results :
case 0 :
self . child . close ()
2022-03-30 19:51:54 -03:00
self . result = True
2022-03-30 17:36:27 -03:00
output = output + self . child . before . decode () + self . child . after . decode ()
output = output . lstrip ()
self . output = output
2022-03-28 10:20:00 -03:00
return True
2022-03-30 17:36:27 -03:00
case 1 | 2 :
2022-03-28 10:20:00 -03:00
self . child . close ()
2022-03-30 19:51:54 -03:00
self . result = False
2022-03-30 17:36:27 -03:00
if results == 1 :
output = output + self . child . before . decode () + self . child . after . decode ()
elif results == 2 :
output = output + self . child . before . decode ()
output = output . lstrip ()
self . output = output
2022-03-28 10:20:00 -03:00
return False
else :
2022-03-30 19:51:54 -03:00
self . result = None
2022-03-30 17:36:27 -03:00
self . output = connect
2022-03-28 10:20:00 -03:00
return connect
2022-03-22 19:54:05 -03:00
def _connect ( self , debug = False ):
2022-03-17 19:05:23 -03:00
if self . protocol == "ssh" :
cmd = "ssh"
if self . idletime > 0 :
cmd = cmd + " -o ServerAliveInterval=" + str ( self . idletime )
if self . user == '' :
cmd = cmd + " -t {} " . format ( self . host )
else :
cmd = cmd + " -t {} " . format ( "@" . join ([ self . user , self . host ]))
if self . port != '' :
cmd = cmd + " -p " + self . port
if self . options != '' :
cmd = cmd + " " + self . options
if self . logs != '' :
2022-03-18 15:32:48 -03:00
self . logfile = self . _logfile ()
2022-03-17 19:05:23 -03:00
if self . password [ 0 ] != '' :
passwords = self . __passtx ( self . password )
else :
passwords = []
2022-03-28 10:20:00 -03:00
expects = [ 'yes/no' , 'refused' , 'supported' , 'cipher' , 'sage' , 'timeout' , 'unavailable' , 'closed' , '[p|P]assword:|[u|U]sername:' , '>$|#$|\$$|>.$|#.$|\$.$' , 'suspend' , pexpect . EOF , "No route to host" , "resolve hostname" ]
2022-03-17 19:05:23 -03:00
elif self . protocol == "telnet" :
cmd = "telnet " + self . host
if self . port != '' :
cmd = cmd + " " + self . port
if self . options != '' :
cmd = cmd + " " + self . options
if self . logs != '' :
2022-03-18 15:32:48 -03:00
self . logfile = self . _logfile ()
2022-03-17 19:05:23 -03:00
if self . password [ 0 ] != '' :
passwords = self . __passtx ( self . password )
else :
passwords = []
2022-03-28 10:20:00 -03:00
expects = [ '[u|U]sername:' , 'refused' , 'supported' , 'cipher' , 'sage' , 'timeout' , 'unavailable' , 'closed' , '[p|P]assword:' , '>$|#$|\$$|>.$|#.$|\$.$' , 'suspend' , pexpect . EOF , "No route to host" , "resolve hostname" ]
2022-03-17 19:05:23 -03:00
else :
2022-03-25 12:25:59 -03:00
raise ValueError ( "Invalid protocol: " + self . protocol )
2022-03-17 19:05:23 -03:00
child = pexpect . spawn ( cmd )
2022-03-22 19:54:05 -03:00
if debug :
child . logfile_read = sys . stdout . buffer
2022-03-17 19:05:23 -03:00
if len ( passwords ) > 0 :
loops = len ( passwords )
else :
loops = 1
endloop = False
for i in range ( 0 , loops ):
while True :
results = child . expect ( expects )
match results :
case 0 :
if self . protocol == "ssh" :
child . sendline ( 'yes' )
elif self . protocol == "telnet" :
if self . user != '' :
child . sendline ( self . user )
else :
2022-03-18 15:32:48 -03:00
self . missingtext = True
2022-03-17 19:05:23 -03:00
break
2022-03-25 17:55:43 -03:00
case 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 :
2022-03-17 19:05:23 -03:00
child . close ()
2022-03-25 12:25:59 -03:00
return "Connection failed code:" + str ( results )
2022-03-17 19:05:23 -03:00
case 8 :
if len ( passwords ) > 0 :
child . sendline ( passwords [ i ])
else :
2022-03-18 15:32:48 -03:00
self . missingtext = True
2022-03-17 19:05:23 -03:00
break
2022-03-18 15:32:48 -03:00
case 9 | 11 :
2022-03-17 19:05:23 -03:00
endloop = True
child . sendline ()
break
case 10 :
child . sendline ( " \r " )
sleep ( 2 )
if endloop :
break
child . readline ( 0 )
2022-03-18 15:32:48 -03:00
self . child = child
return True
2022-03-30 19:51:54 -03:00
class nodes :
def __init__ ( self , nodes : dict , config = '' ):
self . nodelist = []
self . config = config
for n in nodes :
self . nodelist . append ( node ( n , ** nodes [ n ], config = config ))
def splitlist ( self , lst , n ):
for i in range ( 0 , len ( lst ), n ):
yield lst [ i : i + n ]
def run ( self , commands , * , folder = None , prompt = None , stdout = None , parallel = 10 ):
args = {}
args [ "commands" ] = commands
if folder != None :
args [ "folder" ] = folder
if prompt != None :
args [ "prompt" ] = prompt
if stdout != None :
args [ "stdout" ] = stdout
output = {}
tasks = []
for n in self . nodelist :
tasks . append ( threading . Thread ( target = n . run , kwargs = args ))
taskslist = list ( self . splitlist ( tasks , parallel ))
for t in taskslist :
for i in t :
i . start ()
for i in t :
i . join ()
for i in self . nodelist :
output [ i . id ] = i . output
self . output = output
return output
def test ( self , commands , expected , * , prompt = None , parallel = 10 ):
args = {}
args [ "commands" ] = commands
args [ "expected" ] = expected
if prompt != None :
args [ "prompt" ] = prompt
output = {}
result = {}
tasks = []
for n in self . nodelist :
tasks . append ( threading . Thread ( target = n . test , kwargs = args ))
taskslist = list ( self . splitlist ( tasks , parallel ))
for t in taskslist :
for i in t :
i . start ()
for i in t :
i . join ()
for i in self . nodelist :
result [ i . id ] = i . result
output [ i . id ] = i . output
self . output = output
self . result = result
return result
2022-03-17 19:05:23 -03:00
# script