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