def do_send(self, data:str="") -> None: """ send { file filename | string } Sends stuff over the channel. """ if not self.hop.channel: gkf.tombstone(red('channel not open.')) self.do_help('send') return if data.startswith('file'): try: _, filename = data.split() f = fname.Fname(filename) if f: data=f() except Exception as e: gkf.tombstone(red(gkf.type_and_text(e))) try: i = self.hop.channel.send(data) gkf.tombstone(blue('sent {} bytes.'.format(i))) except KeyboardInterrupt: gkf.tombstone(blue('aborting. Control-C pressed.')) except Exception as e: gkf.tombstone(red(gkf.type_and_text(e))) finally: self.hop.open_channel()
def do_do(self, data:str="") -> None: """ do { something } attempt to exit a command by stuffing the text through the channel """ if not self.hop.channel or not data: self.do_help('do') return try: gkf.tombstone(blue('attempting remote command {}'.format(data))) in_, out_, err_ = self.hop.channel.exec_command(data) except KeyboardInterrupt as e: gkf.tombstone(blue('aborting. Control-C pressed.')) except Exception as e: gkf.tombstone(red(gkf.type_and_text(e))) else: out_.channel.recv_exit_status(); gkf.tombstone(blue(out_.readlines())) finally: self.hop.open_channel()
def do_put(self, data:str="") -> None: """ put a file onto the remote host. Syntax: put filename NOTE: filename can be a wildcard spec. """ if not self.hop.sftp: gkf.tombstone(red('sftp channel is not open.')) return if not data: gkf.tombstone(red('you have to send something ...')) self.do_help('put') return files = glob.glob(data) if not files: gkf.tombstone(red('no file[s] named {}'.format(data))) return start_time = time.time() OK = None for f in files: try: OK = self.hop.sftp.put(f.fqn, f.fname) except Exception as e: gkf.tombstone(red(gkf.type_and_text(e))) stop_time = time.time() if OK: gkf.tombstone('success') else: gkf.tombstone('failure {}'.format(self.hop.error_msg())) gkf.tombstone('elapsed time: '.format(elapsed_time(stop_time, start_time)))
def open_socket(self, host:str, port:int=None) -> bool: """ Attemps to open a new socket with the current parameters. """ self.error = None self.ssh_info = gkf.get_ssh_host_info(host) if not self.ssh_info: self.error = 'unknown host' return False hostname = self.ssh_info['hostname'] try: port = int(port) except: port = int(self.ssh_info.get('port', 22)) self.sock = socket.socket(self.sock_domain, self.sock_type) try: self.sock.settimeout(self.tcp_timeout) self.sock.connect((hostname,port)) except socket.timeout as e: self.error = 'timeout of {} seconds exceeded.'.format(self.tcp_timeout) except Exception as e: self.error = gkf.type_and_text(e) else: self.remote_host = hostname self.remote_port = port return self.error is None
def attach_IO(self, f, strip_comments=False) -> JSONReader: """ This function treats a string argument as a filename that needs to be opened, and treats a file as something that needs to be read. The function does not /parse/ the contents, but it does return self for ease of chaining. Raises URException if no file exists, or if it cannot be opened for read access, or if the argument is an incompatible type. """ try: x = open(f, 'r') self.s = x.read() x.close() if strip_comments: self.comment_stripper() except AttributeError as e: # This tests for a filename x = fname.Fname(f) if not x: raise Exception("No source named " + str(x) + " exists.") self.origin = str(x) self.attach_IO(self.origin, strip_comments) except IOError as e: gkf.tombstone(gkf.type_and_text(e)) raise e from None return self
def open_sftp(self, data:list=[]) -> bool: """ Open an sftp connection to the remote host. """ self.error = None try: self.sftp = paramiko.SFTPClient.from_transport(self.transport) except Exception as e: self.error = gkf.type_and_text(e) finally: return self.error is None
def open_session(self) -> bool: """ Attempt to create an SSH session with the remote host using the socket, transport, and channel that we [may] have already openend. """ global members self.error = None self.client = SSHClient() self.client.load_system_host_keys() self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy) try: username=self.ssh_info.get('user', getpass.getuser()) if not self.password: self.client.connect(self.ssh_info['hostname'], int(self.ssh_info['port']), username=username, key_filename=self.ssh_info['identityfile'], sock=self.sock) else: self.client.connect(self.ssh_info['hostname'], int(self.ssh_info['port']), username=username, password=self.password, sock=self.sock) except paramiko.ssh_exception.BadAuthenticationType as e: self.error = str(e) except TypeError as e: gkf.tombstone(red('Socket not open.')) self.error = -1 except Exception as e: self.error = gkf.type_and_text(e) else: self.open_transport() opts = self.transport.get_security_options() self.security = { k:list(getattr(opts, k, None)) for k in members } self.security['host_key'] = self.transport.get_remote_server_key().get_base64() self.security['version'] = self.transport.remote_version finally: return self.error is None
def do_probe(self, data:str="") -> None: """ Syntax: probe {host} [ host, [host] .. ] The 'probe' is nothing more than a convenience. It connects to a host with logging on and set to the debug level. The transaction is appended to the logfile for later inspection. Each probe is given a 9-digit random ID. In the logfile, you will find a BEGIN TRANSACTION and an END TRANSACTION containing the information that is gleaned from the probe. """ self.do_logging('on') self.do_debug('10') hostnames = data.strip().split() if not hostnames: self.do_help('probe') return if 'all' in hostnames: hostnames = sorted(list(gkf.get_ssh_host_info('all'))) hostnames.remove('*') transaction_log = open('beachhead.log', 'a') transaction_id = "{:0>9}".format(random.randrange(1000000000)) try: transaction_log.write('BEGIN TRANSACTION {}\n'.format(transaction_id)) transaction_log.flush() for _ in hostnames: gkf.tombstone('probing {}'.format(_)) transaction_log.write('probing {}\n'.format(_)) transaction_log.flush() try: self.do_open('socket {}'.format(_)) if not self.hop: continue self.do_open('session') self.do_close() except Exception as e: gkf.tombstone(gkf.type_and_text(e)) finally: transaction_log.write('END TRANSACTION {}\n'.format(transaction_id)) transaction_log.flush() transaction_log.close() gkf.tombstone("Written to logfile as transaction ID {}".format(transaction_id))
def do_connect(self, info: str = "") -> None: """ Usage: connect {IP|name} {port} """ info = info.strip().split() self.connection = beachhead.SocketConnection() try: self.connection.open_socket(info[0], int(info[1])) except Exception as e: gkf.tombstone(gkf.type_and_text(e)) return print('Connected to /something/ at {}:{}'.format(info[0], info[1])) print('Ready to talk. Sending TEST') self.connection.send('TEST') reply = self.read() print('Received {} as reply'.format(reply))
def do_logging(self, data:str="") -> None: """ Usage: logging { on | off } turns logging (to $PWD/beachhead.log) on or off. No error is created when logging is on and you ask to turn it on, etc. If you would like to specify a different logfile, there are two solutions. [1] Symbolic links: rm -f $PWD/beachhead.log ln -s yourfile $PWD/beachhead.log [2] Use a different program. """ states = { "on":True, "off":False } if not data: self.do_help('logging') return try: state = states.get(data.lower(), None) if state is None: raise StopIteration from None except StopIteration as e: self.do_help('logging') return except Exception as e: gkf.tombstone(gkf.type_and_text(e)) return if state: logging.getLogger("paramiko").setLevel(logging.WARNING) paramiko.util.log_to_file("beachhead.log") else: logging.getLogger("paramiko").setLevel(logging.NOTSET) return
def open_transport(self, data:str="") -> bool: """ Creates a transport layer from an open/active ssh session. """ self.error = None if not self.client: self.error = 'no open session for transport' return False try: self.transport = self.client.get_transport() except Exception as e: self.error = gkf.type_and_text(e) finally: return self.error is None
def open_channel(self, channel_type:str="session") -> bool: """ Acquire a channel of the desired type. "session" is the default. """ self.error = None channel_types = { "session":"session", "forward":"forwarded-tcpip", "direct":"direct-tcpip", "x":"x11" } if data not in channel_types.keys() and data not in channel_types.values(): self.error = 'unknown channel type: {}'.format(data) return False try: self.channel = self.transport.open_channel(data) except Exception as e: self.error = gkf.type_and_text(e) finally: return self.error is not None
def preloop(self) -> None: """ Get the config (if any). This function updates the self.cfg_file class member with the full file name of the one that we will use. """ setproctitle.setproctitle('beachhead') default_ssh_config_file = '~/.ssh/config' f = fname.Fname(default_ssh_config_file) if not f: gkf.tombstone('You do not seem to have an ssh config file. This program') gkf.tombstone('may not be very useful.') try: for d in [ os.environ.get(_, "") for _ in [ 'PWD' ] ]: for this_dir, sub_dirs, files in os.walk(d): for f in files: if f == __default_config__: self.cfg_file = os.path.join(d, this_dir, f) raise StopIteration except StopIteration as e: try: jp = jparse.JSONReader() self.cfg = jp.attach_IO(self.cfg_file, True).convert() gkf.tombstone('Using config info read from {}'.format(self.cfg_file)) except Exception as e: gkf.tombstone(str(e)) gkf.tombstone('{} failed to compile.'.format(self.cfg_file)) except Exception as e: gkf.tombstone(gkf.type_and_text(e)) gkf.tombstone('Something really bad happened.') raise else: gkf.tombstone('No config file found.')
print('Connected to /something/ at {}:{}'.format(info[0], info[1])) print('Ready to talk. Sending TEST') self.connection.send('TEST') reply = self.read() print('Received {} as reply'.format(reply)) def do_exit(self, info: str = "") -> None: """ Usage: exit """ if self.connection: self.connection.close() sys.exit(os.EX_OK) if __name__ == "__main__": # subprocess.call('clear',shell=True) while True: try: Davis().cmdloop() except KeyboardInterrupt: gkf.tombstone("Exiting via control-C.") sys.exit(os.EX_OK) except Exception as e: gkf.tombstone(gkf.type_and_text(e)) gkf.tombstone(gkf.formatted_stack_trace(True)) sys.exit(1)