diff --git a/gateway.py b/gateway.py index 03fe4ca..0daf1df 100644 --- a/gateway.py +++ b/gateway.py @@ -9,6 +9,8 @@ import datetime import random import hashlib import subprocess +import gzip +import base64 import re import requests import atexit @@ -75,6 +77,7 @@ class Gateway(): self.nonce_key = None self.stok = None # HTTP session token self.status = -2 + self.errcode = -1 self.ftp = None self.socket = None # TCP socket for SSH self.ssh = None # SSH session @@ -898,6 +901,8 @@ class Gateway(): def run_cmd(self, command, msg = None, timeout = None, die_on_error = True): error = 0 + reslist = [ ] + self.errcode = -1 if self.use_ssh: ssh = self.get_ssh(self.verbose) else: @@ -911,6 +916,8 @@ class Gateway(): cmdlist = command if not cmdlist: raise ValueError('Incorrect command list') + if '\n' in ';'.join(cmdlist): + raise ValueError('Incorrect command format (1)') for idx, cmd in enumerate(cmdlist): if self.use_ssh: channel = ssh.open_session() @@ -935,16 +942,41 @@ class Gateway(): except Exception: pass #status = channel.get_exit_status() + self.errcode = 0 + reslist.append('') else: # telnet - cmd += '\n' - tn.write(cmd.encode('ascii')) - tn.read_until(tn.prompt, timeout = 4 if timeout is None else timeout) + end = b'echo -e "\\xA6$?\\xA7\\xB8_END"' + try: + tn.write(cmd.encode('latin1') + b'\n' + end + b'\n') + res = tn.read_until(b'\xA7\xB8_END\r\n' + tn.prompt, timeout = 4 if timeout is None else timeout) + p1 = res.find(b'\r\n') + p2 = res.rfind(b'\r\n' + tn.prompt + end) + p3 = res.rfind(b'\r\n\xA6') + p4 = res.rfind(b'\xA7\xB8_END') + if p1 < 0 or p2 < 0 or p3 < 0 or p4 < 0 or p3 >= p4: + if die_on_error: + die('TELNET: execute command: incorrect response (1)') + reslist.append(None) + self.errcode = -2 + else: + self.errcode = int(res[p3+3:p4].decode(), 10) + reslist.append(res[p1+2:p2].decode().replace('\r\n', '\n')) + except Exception as e: + error = -4 + if die_on_error: + die(f'TELNET: execute command error: "{e}"') if error != 0: break - if not self.use_ssh: - tn.write(b"exit\n") - tn.close() - return True if error == 0 else None + if not self.use_ssh: # telnet + try: + tn.write(b"exit\n") + tn.close() + except Exception: + pass + if error == 0: + return reslist if isinstance(command, list) else reslist[0] + else: + return None def download(self, fn_remote, fn_local, verbose = 1): if verbose and self.verbose: @@ -965,11 +997,44 @@ class Gateway(): read_size += size elif self.use_ftp: ftp = self.get_ftp(self.verbose) - file = open(fn_local, 'wb') - ftp.retrbinary('RETR ' + fn_remote, file.write) - file.close() - else: - raise RuntimeError('FIXME') + with open(fn_local, 'wb') as file: + ftp.retrbinary('RETR ' + fn_remote, file.write) + else: # telnet + os.remove(fn_local) if os.path.exists(fn_local) else None + size = self.get_remote_file_size(fn_remote) + if size < 0: + return False + if size == 0: + with open(fn_local, 'wb') as file: + pass + return True + tn = self.get_telnet(self.verbose) + fin = b" ; echo 'X''_FIN_''X'" + blksize = 64*1000 + filesize = 0 + with open(fn_local, 'wb') as file: + while filesize < size: + count = blksize + if filesize + count > size: + count = size - filesize + cmd = f"dd if='{fn_remote}' iflag=skip_bytes,count_bytes skip={filesize} count={count} 2>/dev/null | base64" + tn.write(cmd.encode('latin1') + fin + b'\n') + res = tn.read_until(b'X_FIN_X\r\n' + tn.prompt, timeout = 4) + p1 = res.find(b'\r\n') + p2 = res.rfind(b'X_FIN_X\r\n') + if p1 < 0 or p2 < 0 or p1 >= p2: + break + data = base64.standard_b64decode(res[p1+2:p2].replace(b'\r\n', b'')) + filesize += len(data) + file.write(data) + try: + tn.write(b"exit\n") + tn.close() + except Exception: + pass + if filesize != size: + os.remove(fn_local) if os.path.exists(fn_local) else None + return False return True def upload(self, fn_local, fn_remote, md5chk = True, verbose = 1): @@ -977,7 +1042,6 @@ class Gateway(): die(f'File "{fn_local}" not found.') if md5chk: md5_local = self.get_md5_for_local_file(fn_local) - file = open(fn_local, 'rb') if verbose and self.verbose: print(f'Upload file: "{fn_local}" ....') if self.use_ssh: @@ -985,17 +1049,48 @@ class Gateway(): finfo = os.stat(fn_local) channel = ssh.scp_send64(fn_remote, finfo.st_mode & 0o777, finfo.st_size, finfo.st_mtime, finfo.st_atime) size = 0 - if True: + with open(fn_local, 'rb') as file: for data in file: channel.write(data) size = size + len(data) #except ssh2.exceptions.SCPProtocolError as e: elif self.use_ftp: ftp = self.get_ftp(self.verbose) - ftp.storbinary('STOR ' + fn_remote, file) - else: - raise RuntimeError('FIXME') - file.close() + with open(fn_local, 'rb') as file: + ftp.storbinary('STOR ' + fn_remote, file) + else: # telnet + with open(fn_local, 'rb') as file: + data = file.read() + data = gzip.compress(data, compresslevel = 9) + data = base64.standard_b64encode(data) + fn_remote_tmp = '/tmp/_T_' + os.path.basename(fn_remote) + if len(fn_remote_tmp) > 80: + raise ValueError('Incorrect length of filename') + self.run_cmd(f"rm -f '{fn_remote_tmp}'") + tn = self.get_telnet(self.verbose) + filesize = 0 + while filesize < len(data): + chunk = data[filesize:filesize+400] + if len(chunk) > 0: + fopt = b'>' if filesize == 0 else b'>>' + cmd = b"echo -n " + chunk + fopt + fn_remote_tmp.encode() + tn.write(cmd + b'\n') + res = tn.read_until(b'\r\n' + tn.prompt, timeout = 4) + p1 = res.find(b'echo -n ') + p2 = res.rfind(b'\r\n' + tn.prompt) + if p1 < 0 or p2 < 0 or p1 >= p2 or p2 - p1 != len(cmd): + break + filesize += len(chunk) + try: + tn.write(b"exit\n") + tn.close() + except Exception: + pass + if filesize != len(data): + self.run_cmd(f"rm -f '{fn_remote_tmp}'") + return False + self.run_cmd(f"rm -f '{fn_remote}' ; cat '{fn_remote_tmp}' | base64 -d | gzip -d > '{fn_remote}'") + self.run_cmd(f"rm -f '{fn_remote_tmp}'") if md5chk: md5_remote = self.get_md5_for_remote_file(fn_remote) if md5_remote != md5_local: @@ -1006,6 +1101,12 @@ class Gateway(): return False return True + def get_remote_file_size(self, fn_remote): + size = self.run_cmd(f"wc -c '{fn_remote}' 2>/dev/null" + " | awk '{print $1}'") + if not size: + return -1 + return int(size, 10) + def get_md5_for_remote_file(self, fn_remote, timeout = 8): fname = os.path.basename(fn_remote) num = str(random.randint(10000, 1000000))