Files
connpy/connpy/core.py
T

783 lines
32 KiB
Python
Raw Normal View History

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
2022-05-25 17:25:02 -03:00
from time import sleep,time
2022-03-17 19:05:23 -03:00
import datetime
import sys
2022-03-30 19:51:54 -03:00
import threading
from pathlib import Path
2022-05-11 14:25:43 -03:00
from copy import deepcopy
2022-05-25 17:25:02 -03:00
import io
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-04-02 23:25:53 -03:00
''' This class generates a node object. Containts all the information and methods to connect and interact with a device using ssh or telnet.
2022-04-03 10:26:08 -03:00
### Attributes:
- output (str): Output of the commands you ran with run or test
method.
2022-04-02 23:25:53 -03:00
2022-04-03 10:26:08 -03:00
- result(bool): True if expected value is found after running
the commands using test method.
2022-04-23 17:11:38 -03:00
- status (int): 0 if the method run or test run succesfully.
1 if connection failed.
2 if expect timeouts without prompt or EOF.
2022-04-02 23:25:53 -03:00
'''
2023-12-01 18:30:29 -03:00
def __init__(self, unique, host, options='', logs='', password='', port='', protocol='', user='', config='', tags='', jumphost=''):
2022-04-02 23:25:53 -03:00
'''
2022-04-03 10:26:08 -03:00
### Parameters:
- unique (str): Unique name to assign to the node.
- host (str): IP address or hostname of the node.
### Optional Parameters:
- options (str): Additional options to pass the ssh/telnet for
connection.
- logs (str): Path/file for storing the logs. You can use
${unique},${host}, ${port}, ${user}, ${protocol}
as variables.
- password (str): Encrypted or plaintext password.
- port (str): Port to connect to node, default 22 for ssh and 23
for telnet.
- protocol (str): Select ssh or telnet. Default is ssh.
- user (str): Username to of the node.
- config (obj): Pass the object created with class configfile with
key for decryption and extra configuration if you
are using connection manager.
- tags (dict) : Tags useful for automation and personal porpuse
like "os", "prompt" and "screenleght_command"
2023-12-01 18:30:29 -03:00
- jumphost (str): Reference another node to be used as a jumphost
2022-04-02 23:25:53 -03:00
'''
2022-03-18 15:32:48 -03:00
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
2023-12-01 18:30:29 -03:00
attr = {"host": host, "logs": logs, "options":options, "port": port, "protocol": protocol, "user": user, "tags": tags, "jumphost": jumphost}
2022-03-17 19:05:23 -03:00
for key in attr:
profile = re.search("^@(.*)", str(attr[key]))
2022-03-18 15:32:48 -03:00
if profile and config != '':
try:
setattr(self,key,config.profiles[profile.group(1)][key])
except:
setattr(self,key,"")
2022-03-17 19:05:23 -03:00
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]
2023-12-01 18:30:29 -03:00
if self.jumphost != "" and config != '':
self.jumphost = config.getitem(self.jumphost)
for key in self.jumphost:
profile = re.search("^@(.*)", str(self.jumphost[key]))
if profile:
try:
self.jumphost[key] = config.profiles[profile.group(1)][key]
except:
self.jumphost[key] = ""
elif self.jumphost[key] == '' and key == "protocol":
try:
self.jumphost[key] = config.profiles["default"][key]
except:
self.jumphost[key] = "ssh"
if isinstance(self.jumphost["password"],list):
jumphost_password = []
for i, s in enumerate(self.jumphost["password"]):
profile = re.search("^@(.*)", self.jumphost["password"][i])
if profile:
jumphost_password.append(config.profiles[profile.group(1)]["password"])
self.jumphost["password"] = jumphost_password
else:
self.jumphost["password"] = [self.jumphost["password"]]
if self.jumphost["password"] != [""]:
self.password = self.jumphost["password"] + self.password
if self.jumphost["protocol"] == "ssh":
jumphost_cmd = self.jumphost["protocol"] + " -W %h:%p"
2023-12-04 11:11:58 -03:00
if self.jumphost["port"] != '':
2023-12-01 18:30:29 -03:00
jumphost_cmd = jumphost_cmd + " -p " + self.jumphost["port"]
2023-12-04 11:11:58 -03:00
if self.jumphost["options"] != '':
2023-12-01 18:30:29 -03:00
jumphost_cmd = jumphost_cmd + " " + self.jumphost["options"]
2023-12-04 11:11:58 -03:00
if self.jumphost["user"] == '':
2023-12-01 18:30:29 -03:00
jumphost_cmd = jumphost_cmd + " {}".format(self.jumphost["host"])
else:
jumphost_cmd = jumphost_cmd + " {}".format("@".join([self.jumphost["user"],self.jumphost["host"]]))
self.jumphost = f"-o ProxyCommand=\"{jumphost_cmd}\""
else:
self.jumphost = ""
2022-03-17 19:05:23 -03:00
2022-06-10 13:24:26 -03:00
def _passtx(self, passwords, *, keyfile=None):
2022-04-02 23:25:53 -03:00
# decrypts passwords, used by other methdos.
2022-03-17 19:05:23 -03:00
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-06-10 13:24:26 -03:00
with open(keyfile) as f:
key = RSA.import_key(f.read())
2022-03-18 15:32:48 -03:00
decryptor = PKCS1_OAEP.new(key)
2022-03-17 19:05:23 -03:00
for passwd in passwords:
if not re.match('^b[\"\'].+[\"\']$', passwd):
2022-03-18 15:32:48 -03:00
dpass.append(passwd)
else:
try:
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):
2022-04-02 23:25:53 -03:00
# translate logs variables and generate logs path.
2022-03-17 19:05:23 -03:00
if logfile == None:
logfile = self.logs
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):
2022-04-02 23:25:53 -03:00
#Remove special ascii characters and other stuff from logfile.
2022-03-18 16:16:31 -03:00
if var == False:
t = open(logfile, "r").read()
else:
t = logfile
while t.find("\b") != -1:
t = re.sub('[^\b]\b', '', t)
2022-05-19 17:30:05 -03:00
t = t.replace("\n","",1)
t = t.replace("\a","")
2022-03-17 19:05:23 -03:00
t = t.replace('\n\n', '\n')
2022-04-02 23:25:53 -03:00
t = re.sub(r'.\[K', '', t)
2022-03-18 15:32:48 -03:00
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/ ]*[@-~])')
t = ansi_escape.sub('', t)
t = t.lstrip(" \n\r")
2022-05-19 17:30:05 -03:00
t = t.replace("\r","")
2022-06-10 13:24:26 -03:00
t = t.replace("\x0E","")
t = t.replace("\x0F","")
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
2023-10-05 15:21:17 -03:00
def _savelog(self):
'''Save the log buffer to the file at regular intervals if there are changes.'''
t = threading.current_thread()
prev_size = 0 # Store the previous size of the buffer
while getattr(t, "do_run", True): # Check if thread is signaled to stop
current_size = self.mylog.tell() # Current size of the buffer
# Only save if the buffer size has changed
if current_size != prev_size:
with open(self.logfile, "w") as f: # Use "w" to overwrite the file
f.write(self._logclean(self.mylog.getvalue().decode(), True))
prev_size = current_size # Update the previous size
sleep(5)
2022-05-25 17:25:02 -03:00
def _filter(self, a):
#Set time for last input when using interact
self.lastinput = time()
return a
def _keepalive(self):
#Send keepalive ctrl+e when idletime passed without new inputs on interact
self.lastinput = time()
2022-06-10 13:24:26 -03:00
t = threading.current_thread()
2022-05-25 17:25:02 -03:00
while True:
if time() - self.lastinput >= self.idletime:
self.child.sendcontrol("e")
self.lastinput = time()
sleep(1)
2022-03-22 19:54:05 -03:00
def interact(self, debug = False):
2022-04-02 23:25:53 -03:00
'''
Allow user to interact with the node directly, mostly used by connection manager.
2022-04-03 10:26:08 -03:00
### Optional Parameters:
2022-04-02 23:25:53 -03:00
2022-04-03 10:26:08 -03:00
- debug (bool): If True, display all the connecting information
before interact. Default False.
2022-04-02 23:25:53 -03:00
'''
2022-03-22 19:54:05 -03:00
connect = self._connect(debug = debug)
2022-03-18 15:32:48 -03:00
if connect == True:
2023-12-01 18:30:29 -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):
2023-10-05 15:21:17 -03:00
# Initialize self.mylog
2022-05-25 17:25:02 -03:00
if not 'mylog' in dir(self):
self.mylog = io.BytesIO()
self.child.logfile_read = self.mylog
2023-10-05 15:21:17 -03:00
# Start the _savelog thread
log_thread = threading.Thread(target=self._savelog)
log_thread.daemon = True
log_thread.start()
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-05-25 17:25:02 -03:00
if self.idletime > 0:
x = threading.Thread(target=self._keepalive)
x.daemon = True
x.start()
if debug:
print(self.mylog.getvalue().decode())
self.child.interact(input_filter=self._filter)
2023-10-05 15:21:17 -03:00
if 'logfile' in dir(self):
2022-05-25 17:25:02 -03:00
with open(self.logfile, "w") as f:
2023-10-05 15:21:17 -03:00
f.write(self._logclean(self.mylog.getvalue().decode(), True))
2022-05-25 17:25:02 -03:00
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
2023-01-05 16:39:22 -03:00
def run(self, commands, vars = None,*, folder = '', prompt = r'>$|#$|\$$|>.$|#.$|\$.$', stdout = False, timeout = 10):
2022-04-02 23:25:53 -03:00
'''
Run a command or list of commands on the node and return the output.
2022-04-03 10:26:08 -03:00
### Parameters:
- commands (str/list): Commands to run on the node. Should be
2022-04-23 17:11:38 -03:00
str or a list of str. You can use variables
as {varname} and defining them in optional
parameter vars.
### Optional Parameters:
- vars (dict): Dictionary containing the definition of variables
used in commands parameter.
Keys: Variable names.
Values: strings.
2022-04-02 23:25:53 -03:00
2022-04-03 10:26:08 -03:00
### Optional Named Parameters:
2022-04-02 23:25:53 -03:00
2022-04-03 10:26:08 -03:00
- folder (str): Path where output log should be stored, leave
empty to disable logging.
2022-04-02 23:25:53 -03:00
2022-04-03 10:26:08 -03:00
- prompt (str): Prompt to be expected after a command is finished
running. Usually linux uses ">" or EOF while
routers use ">" or "#". The default value should
work for most nodes. Change it if your connection
need some special symbol.
2022-04-02 23:25:53 -03:00
2022-04-03 10:26:08 -03:00
- stdout (bool):Set True to send the command output to stdout.
default False.
2022-04-02 23:25:53 -03:00
2022-04-23 17:11:38 -03:00
- timeout (int):Time in seconds for expect to wait for prompt/EOF.
2023-03-21 18:23:29 -03:00
default 10.
2022-04-23 17:11:38 -03:00
2022-04-03 10:26:08 -03:00
### Returns:
str: Output of the commands you ran on the node.
2022-04-02 23:25:53 -03:00
'''
2022-04-23 17:11:38 -03:00
connect = self._connect(timeout = timeout)
now = datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')
2022-03-18 15:32:48 -03:00
if connect == True:
if "prompt" in self.tags:
prompt = self.tags["prompt"]
2022-04-23 17:11:38 -03:00
expects = [prompt, pexpect.EOF, pexpect.TIMEOUT]
2022-03-18 15:32:48 -03:00
output = ''
status = ''
2022-04-23 17:11:38 -03:00
if not isinstance(commands, list):
commands = [commands]
if "screen_length_command" in self.tags:
commands.insert(0, self.tags["screen_length_command"])
2022-05-25 17:25:02 -03:00
self.mylog = io.BytesIO()
self.child.logfile_read = self.mylog
2022-04-23 17:11:38 -03:00
for c in commands:
if vars is not None:
c = c.format(**vars)
result = self.child.expect(expects, timeout = timeout)
self.child.sendline(c)
if result == 2:
break
2022-05-25 17:25:02 -03:00
if not result == 2:
result = self.child.expect(expects, timeout = timeout)
2022-03-28 10:20:00 -03:00
self.child.close()
2022-05-25 17:25:02 -03:00
output = self._logclean(self.mylog.getvalue().decode(), True)
2022-03-25 12:25:59 -03:00
if stdout == True:
print(output)
if folder != '':
with open(folder + "/" + self.unique + "_" + now + ".txt", "w") as f:
2022-03-18 15:32:48 -03:00
f.write(output)
f.close()
2022-03-18 16:16:31 -03:00
self.output = output
2022-05-25 17:25:02 -03:00
if result == 2:
self.status = 2
else:
self.status = 0
2022-03-18 16:16:31 -03:00
return output
2022-03-25 17:55:43 -03:00
else:
2022-03-30 17:36:27 -03:00
self.output = connect
2022-04-23 17:11:38 -03:00
self.status = 1
if stdout == True:
print(connect)
if folder != '':
with open(folder + "/" + self.unique + "_" + now + ".txt", "w") as f:
f.write(connect)
f.close()
2022-03-25 17:55:43 -03:00
return connect
2022-03-18 15:32:48 -03:00
2023-01-05 16:39:22 -03:00
def test(self, commands, expected, vars = None,*, prompt = r'>$|#$|\$$|>.$|#.$|\$.$', timeout = 10):
2022-04-02 23:25:53 -03:00
'''
Run a command or list of commands on the node, then check if expected value appears on the output after the last command.
2022-04-03 10:26:08 -03:00
### Parameters:
- commands (str/list): Commands to run on the node. Should be
2022-04-23 17:11:38 -03:00
str or a list of str. You can use variables
as {varname} and defining them in optional
parameter vars.
2022-04-02 23:25:53 -03:00
2022-04-03 10:26:08 -03:00
- expected (str) : Expected text to appear after running
2022-04-23 17:11:38 -03:00
all the commands on the node.You can use
variables as {varname} and defining them
in optional parameter vars.
### Optional Parameters:
- vars (dict): Dictionary containing the definition of variables
used in commands and expected parameters.
Keys: Variable names.
Values: strings.
2022-04-02 23:25:53 -03:00
2022-04-03 10:26:08 -03:00
### Optional Named Parameters:
2022-04-02 23:25:53 -03:00
2022-04-03 10:26:08 -03:00
- prompt (str): Prompt to be expected after a command is finished
running. Usually linux uses ">" or EOF while
routers use ">" or "#". The default value should
work for most nodes. Change it if your connection
need some special symbol.
2022-04-02 23:25:53 -03:00
2022-04-23 17:11:38 -03:00
- timeout (int):Time in seconds for expect to wait for prompt/EOF.
2023-03-21 18:23:29 -03:00
default 10.
2022-04-23 17:11:38 -03:00
2022-04-03 09:38:00 -03:00
### Returns:
2022-04-03 10:26:08 -03:00
bool: true if expected value is found after running the commands
false if prompt is found before.
2022-04-02 23:25:53 -03:00
'''
2022-04-23 17:11:38 -03:00
connect = self._connect(timeout = timeout)
2022-03-28 10:20:00 -03:00
if connect == True:
if "prompt" in self.tags:
prompt = self.tags["prompt"]
2022-04-23 17:11:38 -03:00
expects = [prompt, pexpect.EOF, pexpect.TIMEOUT]
2022-03-28 10:20:00 -03:00
output = ''
2022-04-23 17:11:38 -03:00
if not isinstance(commands, list):
commands = [commands]
if not isinstance(expected, list):
expected = [expected]
if "screen_length_command" in self.tags:
commands.insert(0, self.tags["screen_length_command"])
2022-05-25 17:25:02 -03:00
self.mylog = io.BytesIO()
self.child.logfile_read = self.mylog
2022-04-23 17:11:38 -03:00
for c in commands:
if vars is not None:
c = c.format(**vars)
result = self.child.expect(expects, timeout = timeout)
self.child.sendline(c)
if result == 2:
2022-05-25 17:25:02 -03:00
break
if not result == 2:
2022-05-25 17:25:02 -03:00
result = self.child.expect(expects, timeout = timeout)
2022-04-23 17:11:38 -03:00
self.child.close()
2022-05-25 17:25:02 -03:00
output = self._logclean(self.mylog.getvalue().decode(), True)
self.output = output
if result in [0, 1]:
# lastcommand = commands[-1]
# if vars is not None:
# lastcommand = lastcommand.format(**vars)
# last_command_index = output.rfind(lastcommand)
# cleaned_output = output[last_command_index + len(lastcommand):].strip()
self.result = {}
for e in expected:
if vars is not None:
e = e.format(**vars)
updatedprompt = re.sub(r'(?<!\\)\$', '', prompt)
newpattern = f".*({updatedprompt}).*{e}.*"
cleaned_output = output
cleaned_output = re.sub(newpattern, '', cleaned_output)
if e in cleaned_output:
self.result[e] = True
else:
self.result[e]= False
2022-04-23 17:11:38 -03:00
self.status = 0
return self.result
if result == 2:
2022-04-23 17:11:38 -03:00
self.result = None
self.status = 2
return output
2022-03-28 10:20:00 -03:00
else:
2022-03-30 19:51:54 -03:00
self.result = None
2022-03-30 17:36:27 -03:00
self.output = connect
2022-04-23 17:11:38 -03:00
self.status = 1
2022-03-28 10:20:00 -03:00
return connect
2023-01-05 16:39:22 -03:00
def _connect(self, debug = False, timeout = 10, max_attempts = 3):
2022-04-02 23:25:53 -03:00
# Method to connect to the node, it parse all the information, create the ssh/telnet command and login to the node.
2023-11-03 11:59:00 -03:00
if self.protocol in ["ssh", "sftp"]:
cmd = self.protocol
2022-03-17 19:05:23 -03:00
if self.idletime > 0:
cmd = cmd + " -o ServerAliveInterval=" + str(self.idletime)
if self.port != '':
2023-11-03 11:59:00 -03:00
if self.protocol == "ssh":
cmd = cmd + " -p " + self.port
elif self.protocol == "sftp":
cmd = cmd + " -P " + self.port
2022-03-17 19:05:23 -03:00
if self.options != '':
cmd = cmd + " " + self.options
if self.logs != '':
2022-03-18 15:32:48 -03:00
self.logfile = self._logfile()
2023-12-01 18:30:29 -03:00
if self.jumphost != '':
cmd = cmd + " " + self.jumphost
2022-03-17 19:05:23 -03:00
if self.password[0] != '':
2022-06-10 13:24:26 -03:00
passwords = self._passtx(self.password)
2022-03-17 19:05:23 -03:00
else:
passwords = []
2023-11-03 11:59:00 -03:00
if self.user == '':
cmd = cmd + " {}".format(self.host)
else:
cmd = cmd + " {}".format("@".join([self.user,self.host]))
2023-12-06 17:46:10 -03:00
expects = ['yes/no', 'refused', 'supported', 'Invalid|[u|U]sage: (ssh|sftp)', 'ssh-keygen.*\"', 'timeout|timed.out', 'unavailable', 'closed', '[p|P]assword:|[u|U]sername:', r'>$|#$|\$$|>.$|#.$|\$.$', 'suspend', pexpect.EOF, pexpect.TIMEOUT, "No route to host", "resolve hostname", "no matching", "[b|B]ad (owner|permissions)"]
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] != '':
2022-06-10 13:24:26 -03:00
passwords = self._passtx(self.password)
2022-03-17 19:05:23 -03:00
else:
passwords = []
2023-12-06 17:46:10 -03:00
expects = ['[u|U]sername:', 'refused', 'supported', 'invalid option', 'ssh-keygen.*\"', 'timeout|timed.out', 'unavailable', 'closed', '[p|P]assword:', r'>$|#$|\$$|>.$|#.$|\$.$', 'suspend', pexpect.EOF, pexpect.TIMEOUT, "No route to host", "resolve hostname", "no matching", "[b|B]ad (owner|permissions)"]
2022-03-17 19:05:23 -03:00
else:
2022-03-25 12:25:59 -03:00
raise ValueError("Invalid protocol: " + self.protocol)
2023-01-05 16:39:22 -03:00
attempts = 1
while attempts <= max_attempts:
child = pexpect.spawn(cmd)
if debug:
print(cmd)
self.mylog = io.BytesIO()
child.logfile_read = self.mylog
if len(passwords) > 0:
loops = len(passwords)
else:
loops = 1
endloop = False
for i in range(0, loops):
while True:
results = child.expect(expects, timeout=timeout)
if results == 0:
2023-11-03 11:59:00 -03:00
if self.protocol in ["ssh", "sftp"]:
2023-01-05 16:39:22 -03:00
child.sendline('yes')
elif self.protocol == "telnet":
if self.user != '':
child.sendline(self.user)
else:
self.missingtext = True
break
2023-12-01 13:40:49 -03:00
if results in [1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15, 16]:
2023-01-05 16:39:22 -03:00
child.terminate()
if results == 12 and attempts != max_attempts:
attempts += 1
endloop = True
break
else:
2023-10-26 17:33:44 -03:00
if results == 12:
after = "Connection timeout"
else:
after = child.after.decode()
2023-11-03 11:59:00 -03:00
return ("Connection failed code:" + str(results) + "\n" + child.before.decode().lstrip() + after + child.readline().decode()).rstrip()
2023-01-05 16:39:22 -03:00
if results == 8:
if len(passwords) > 0:
child.sendline(passwords[i])
2022-03-17 19:05:23 -03:00
else:
2022-03-18 15:32:48 -03:00
self.missingtext = True
2023-01-05 16:39:22 -03:00
break
if results in [9, 11]:
endloop = True
child.sendline()
break
if results == 10:
child.sendline("\r")
sleep(2)
if endloop:
2022-04-01 17:53:51 -03:00
break
2023-01-05 16:39:22 -03:00
if results == 12:
continue
else:
2022-03-17 19:05:23 -03:00
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:
2022-04-02 23:25:53 -03:00
''' This class generates a nodes object. Contains a list of node class objects and methods to run multiple tasks on nodes simultaneously.
### Attributes:
- nodelist (list): List of node class objects passed to the init
function.
- output (dict): Dictionary formed by nodes unique as keys,
output of the commands you ran on the node as
value. Created after running methods run or test.
- result (dict): Dictionary formed by nodes unique as keys, value
is True if expected value is found after running
the commands, False if prompt is found before.
Created after running method test.
2022-04-23 17:11:38 -03:00
- status (dict): Dictionary formed by nodes unique as keys, value:
0 if method run or test ended succesfully.
1 if connection failed.
2 if expect timeouts without prompt or EOF.
2022-04-02 23:25:53 -03:00
- <unique> (obj): For each item in nodelist, there is an attribute
generated with the node unique.
'''
2022-03-30 19:51:54 -03:00
def __init__(self, nodes: dict, config = ''):
2022-04-02 23:25:53 -03:00
'''
### Parameters:
- nodes (dict): Dictionary formed by node information:
Keys: Unique name for each node.
Mandatory Subkeys: host(str).
Optional Subkeys: options(str), logs(str), password(str),
port(str), protocol(str), user(str).
For reference on subkeys check node class.
2022-04-03 10:26:08 -03:00
### Optional Parameters:
2022-04-02 23:25:53 -03:00
- config (obj): Pass the object created with class configfile with key
for decryption and extra configuration if you are using
connection manager.
'''
2022-03-30 19:51:54 -03:00
self.nodelist = []
self.config = config
for n in nodes:
2022-03-31 13:42:25 -03:00
this = node(n, **nodes[n], config = config)
self.nodelist.append(this)
setattr(self,n,this)
2022-03-30 19:51:54 -03:00
2022-04-02 23:25:53 -03:00
def _splitlist(self, lst, n):
#split a list in lists of n members.
2022-03-30 19:51:54 -03:00
for i in range(0, len(lst), n):
yield lst[i:i + n]
2022-04-23 17:11:38 -03:00
def run(self, commands, vars = None,*, folder = None, prompt = None, stdout = None, parallel = 10, timeout = None):
2022-04-02 23:25:53 -03:00
'''
Run a command or list of commands on all the nodes in nodelist.
2022-04-03 10:26:08 -03:00
### Parameters:
2022-04-23 17:11:38 -03:00
- commands (str/list): Commands to run on the nodes. Should be str or
list of str. You can use variables as {varname}
and defining them in optional parameter vars.
### Optional Parameters:
- vars (dict): Dictionary containing the definition of variables for
each node, used in commands parameter.
Keys should be formed by nodes unique names. Use
special key name __global__ for global variables.
Subkeys: Variable names.
Values: strings.
2022-04-03 10:26:08 -03:00
### Optional Named Parameters:
2022-04-23 17:11:38 -03:00
- folder (str): Path where output log should be stored, leave empty
to disable logging.
2022-04-03 10:26:08 -03:00
2022-04-23 17:11:38 -03:00
- prompt (str): Prompt to be expected after a command is finished
running. Usually linux uses ">" or EOF while routers
use ">" or "#". The default value should work for
most nodes. Change it if your connection need some
special symbol.
2022-04-02 23:25:53 -03:00
2022-04-23 17:11:38 -03:00
- stdout (bool): Set True to send the command output to stdout.
Default False.
2022-04-02 23:25:53 -03:00
2022-04-23 17:11:38 -03:00
- parallel (int): Number of nodes to run the commands simultaneously.
Default is 10, if there are more nodes that this
value, nodes are groups in groups with max this
number of members.
- timeout (int): Time in seconds for expect to wait for prompt/EOF.
2023-03-21 18:23:29 -03:00
default 10.
2022-04-03 10:26:08 -03:00
###Returns:
dict: Dictionary formed by nodes unique as keys, Output of the
commands you ran on the node as value.
2022-04-02 23:25:53 -03:00
'''
2022-03-30 19:51:54 -03:00
args = {}
2022-04-23 17:11:38 -03:00
nodesargs = {}
2022-03-30 19:51:54 -03:00
args["commands"] = commands
if folder != None:
args["folder"] = folder
Path(folder).mkdir(parents=True, exist_ok=True)
2022-03-30 19:51:54 -03:00
if prompt != None:
args["prompt"] = prompt
if stdout != None:
args["stdout"] = stdout
2022-04-23 17:11:38 -03:00
if timeout != None:
args["timeout"] = timeout
2022-03-30 19:51:54 -03:00
output = {}
2022-04-23 17:11:38 -03:00
status = {}
2022-03-30 19:51:54 -03:00
tasks = []
for n in self.nodelist:
2022-05-11 14:25:43 -03:00
nodesargs[n.unique] = deepcopy(args)
2022-04-23 17:11:38 -03:00
if vars != None:
nodesargs[n.unique]["vars"] = {}
if "__global__" in vars.keys():
nodesargs[n.unique]["vars"].update(vars["__global__"])
if n.unique in vars.keys():
nodesargs[n.unique]["vars"].update(vars[n.unique])
tasks.append(threading.Thread(target=n.run, kwargs=nodesargs[n.unique]))
2022-04-02 23:25:53 -03:00
taskslist = list(self._splitlist(tasks, parallel))
2022-03-30 19:51:54 -03:00
for t in taskslist:
for i in t:
i.start()
for i in t:
i.join()
for i in self.nodelist:
2022-04-02 23:25:53 -03:00
output[i.unique] = i.output
2022-04-23 17:11:38 -03:00
status[i.unique] = i.status
2022-03-30 19:51:54 -03:00
self.output = output
2022-04-23 17:11:38 -03:00
self.status = status
2022-03-30 19:51:54 -03:00
return output
2022-04-23 17:11:38 -03:00
def test(self, commands, expected, vars = None,*, prompt = None, parallel = 10, timeout = None):
2022-04-02 23:25:53 -03:00
'''
Run a command or list of commands on all the nodes in nodelist, then check if expected value appears on the output after the last command.
2022-04-03 10:26:08 -03:00
### Parameters:
2022-04-23 17:11:38 -03:00
- commands (str/list): Commands to run on the node. Should be str or
list of str.
- expected (str) : Expected text to appear after running all the
commands on the node.
### Optional Parameters:
2022-04-03 10:26:08 -03:00
2022-04-23 17:11:38 -03:00
- vars (dict): Dictionary containing the definition of variables for
each node, used in commands and expected parameters.
Keys should be formed by nodes unique names. Use
special key name __global__ for global variables.
Subkeys: Variable names.
Values: strings.
2022-04-03 10:26:08 -03:00
### Optional Named Parameters:
2022-04-23 17:11:38 -03:00
- prompt (str): Prompt to be expected after a command is finished
running. Usually linux uses ">" or EOF while
routers use ">" or "#". The default value should
work for most nodes. Change it if your connection
need some special symbol.
- parallel (int): Number of nodes to run the commands simultaneously.
Default is 10, if there are more nodes that this
value, nodes are groups in groups with max this
number of members.
- timeout (int): Time in seconds for expect to wait for prompt/EOF.
2023-03-21 18:23:29 -03:00
default 10.
2022-04-02 23:25:53 -03:00
2022-04-03 10:26:08 -03:00
### Returns:
2022-04-02 23:25:53 -03:00
2022-04-03 10:26:08 -03:00
dict: Dictionary formed by nodes unique as keys, value is True if
expected value is found after running the commands, False
if prompt is found before.
2022-04-02 23:25:53 -03:00
'''
2022-03-30 19:51:54 -03:00
args = {}
2022-04-23 17:11:38 -03:00
nodesargs = {}
2022-03-30 19:51:54 -03:00
args["commands"] = commands
args["expected"] = expected
if prompt != None:
args["prompt"] = prompt
2022-04-23 17:11:38 -03:00
if timeout != None:
args["timeout"] = timeout
2022-03-30 19:51:54 -03:00
output = {}
result = {}
2022-04-23 17:11:38 -03:00
status = {}
2022-03-30 19:51:54 -03:00
tasks = []
for n in self.nodelist:
2022-05-11 14:25:43 -03:00
nodesargs[n.unique] = deepcopy(args)
2022-04-23 17:11:38 -03:00
if vars != None:
nodesargs[n.unique]["vars"] = {}
if "__global__" in vars.keys():
nodesargs[n.unique]["vars"].update(vars["__global__"])
if n.unique in vars.keys():
nodesargs[n.unique]["vars"].update(vars[n.unique])
tasks.append(threading.Thread(target=n.test, kwargs=nodesargs[n.unique]))
2022-04-02 23:25:53 -03:00
taskslist = list(self._splitlist(tasks, parallel))
2022-03-30 19:51:54 -03:00
for t in taskslist:
for i in t:
i.start()
for i in t:
i.join()
for i in self.nodelist:
2022-04-02 23:25:53 -03:00
result[i.unique] = i.result
output[i.unique] = i.output
2022-04-23 17:11:38 -03:00
status[i.unique] = i.status
2022-03-30 19:51:54 -03:00
self.output = output
self.result = result
2022-04-23 17:11:38 -03:00
self.status = status
2022-03-30 19:51:54 -03:00
return result
2022-03-17 19:05:23 -03:00
# script