PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /opt/sharedrads/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //opt/sharedrads/remote_dump |
#!/opt/imh-python/bin/python3 __author__ = 'chases' import os import shlex import socket import argparse import sys import base64 import subprocess import threading from typing import IO from time import sleep from urllib.parse import urlencode import requests from requests.adapters import TimeoutSauce from prettytable import PrettyTable from rads import prompt_y_n, is_cpuser, SECURE_USER, SYS_USERS, get_owner from rads.color import red, yellow, green from cpapis import whmapi1, CpAPIError def check_user(username): """Checks whether the user is valid for switching. Taken from /opt/tier1adv/bin/switch""" restricted_users = SYS_USERS sec_user = SECURE_USER if sec_user is not None: # will be None on VPS/Ded restricted_users.append(sec_user) if username in restricted_users: print(red(f"You may not remove {username}.")) return False if not is_cpuser(username): print(f"The user {username} is not a cpanel user.") return False return True def input_parse(): parser = argparse.ArgumentParser() # fmt: off parser.add_argument( '-u', '--user', action='store', dest='user', help='Define your remote cpanel user or reseller', ) parser.add_argument( '-d', '--domain', action='store', dest='domain', default=False, help='set domain if cannot be pulled from listaccount (no reseller)', ) parser.add_argument( '-p', '--pass', action='store', dest='res_pass', default=None, help='Set the password for the remote reseller or cpuser. Not ' 'recommended to pass as an argument, but an option nonetheless.', ) parser.add_argument( '--replace', action='store', dest='replace', default=False, help='Replace a cpanel user with the backup restored.', ) parser.add_argument( '-r', '--host', action='store', dest='remote_host', default=None, help='Remote Hostname.', ) parser.add_argument( '-n', '--nosize', action='store_true', default=False, dest='nosize', help='Ignore size limitations', ) parser.add_argument( '--norestore', action='store_true', default=False, dest='norestore', help='Will not restore the package', ) parser.add_argument( '--skip', action='store', default=False, nargs='+', dest='skip', help='Skip specific accounts. Useful with --all', ) parser.add_argument( '-i', '--ip', action='store', dest='IP', default=False, help='Set the IP address on Restore.', ) parser.add_argument( '-o', '--owner', action='store', dest='owner', default=False, help='Set the Owner on restore', ) parser.add_argument( '-P', '--package', action='store', dest='package', default=False, help='Set the package on restore', ) parser.add_argument( '--all', action='store_true', default=False, dest='all', help='Dump all backups for accounts within size specification', ) parser.add_argument('--version', action='version', version='%(prog)s 2.0') # fmt: on raw = parser.parse_known_args() parsed = raw[0] try: if not parsed.user: parsed.user = input( "Enter the reseller user from the remote host: " ) if not parsed.res_pass: parsed.res_pass = input( "Enter the reseller password for the remote host: " ) if not parsed.remote_host: parsed.remote_host = input("Enter remote hostname or IP address: ") if parsed.replace: if os.path.isfile(f'/var/cpanel/users/{parsed.replace}'): choice = prompt_y_n( f'Are you sure you would like to replace {parsed.replace} ' f'which is owned by: {get_owner(parsed.replace)}' ) if not choice: parsed.replace = False else: parsed.replace = False except KeyboardInterrupt: sys.exit('\nQuitting') return parsed class MyTimeout(TimeoutSauce): def __init__(self, *_args, **kwargs): connect = kwargs.get('connect', 5) read = kwargs.get('read', connect) super().__init__(connect=connect, read=read) def validate_whm_connection(parsed): print( 'Trying to make a socket connection to WHM. If you know this will ', 'not work you can Ctrl ^C to move on', ) sock = socket.socket() address = parsed.remote_host port = 2086 # port number is a number, not string try: sock.connect((address, port)) except KeyboardInterrupt: print('Moving on') return False except Exception as exc: print(exc, file=sys.stderr) print(red(f"Unable to connect to {address} on port {port}.")) return False return True def remote_whm_post(parsed, jsoncall, url_args, **kwargs): user = kwargs.get('user', parsed.user) port = kwargs.get('port', '2087') remote_host = parsed.remote_host if url_args: jsoncall += '?' + urlencode(url_args) url = 'https://' + remote_host + ':' + port + '/' + jsoncall passholder = parsed.res_pass encoded_pass = base64.b64encode(user + ':' + passholder) auth = "Basic " + encoded_pass header = {'Authorization': auth} requests.adapters.TimeoutSauce = MyTimeout try: response = requests.post(url, headers=header, verify=False, timeout=5) except requests.exceptions.ConnectTimeout: return False try: response = response.json() except ValueError: print('Unable to get valid response from', remote_host) print('Response received from ', remote_host, ':', response) return False return response def _write_stdin(stdin: IO, data: str): stdin.write(data) stdin.close() def get_cpanel_backup( parsed, restoreuser=False, restoredomain=False, attempt=0 ): if restoreuser: parsed.user = restoreuser if restoredomain: parsed.domain = restoredomain restorelog = f'/tmp/parsed.{parsed.user}.backuplog' print( "Backup will start in the background. You can monitor it by tail -f", restorelog, ) cmd = [ '/scripts/getremotecpmove', parsed.remote_host, parsed.user, parsed.domain, ] with open(restorelog, 'w', encoding='utf-8') as writelog: with subprocess.Popen( cmd, encoding='utf-8', stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, ) as proc: # write to stdin in a thread so this won't deadlock if the proc # writes > 1024 bytes to stdout/err before consuming stdin thread = threading.Thread( target=_write_stdin, args=(proc.stdin, parsed.res_pass), daemon=True, ) for line in proc.stdout: print(line.strip(), flush=True) writelog.write(line) thread.join() if proc.wait(): # non-zero exit code print(red('Failed to get backup from remote host.')) if attempt < 2: print( red('Waiting 10 minutes and attempting again.'), red('Press ctrl ^c to Manually Fail this user.'), flush=True, ) try: sleep(600) except KeyboardInterrupt: # This user Failed, but continuing on return False attempt += 1 get_cpanel_backup(parsed, attempt=attempt) else: print(red('Too many attempts failed. Moving on to next user.')) return False with open(restorelog, encoding='utf-8') as resultfile: for line in resultfile: if line.startswith('pkgacctfile is'): print('Backup Found: {}'.format(line.split(' ')[2].strip())) return line.split(' ')[2].strip() if 'Failed to fetch cpmove file via cPanel XML-API' in line: return False return False def set_backup_to_standard_naming(backup, user): if os.path.isfile(backup): os.rename(backup, f'/home/cpmove-{user}.tar.gz') return f'/home/cpmove-{user}.tar.gz' return False def restore_backup(backup, user, parsed): # TODO: this script is python. restorepkg is python. Import and inspect the # result directly rather than executing and reading its output str. # Do this after merging the rpms. cmd = ['/opt/tier2c/safe_restorepkg.py'] if parsed.IP: cmd.extend(['--ip', parsed.IP.strip()]) cmd.extend('--quiet', backup) status = 'Success' with subprocess.Popen( cmd, encoding='utf-8', stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) as proc: for line in proc.stdout: print(line.strip()) if 'Failed' in line: status = 'Failed' if line.startswith("{'%s'" % user): try: status = line.split("'")[3] except KeyError: pass if proc.wait(): status = 'Non-Zero Exit' if status == 'Success' and parsed.owner: try: whmapi1.set_owner(user, parsed.owner) except CpAPIError as exc: status = str(exc) return status def main(): parsed = input_parse() if parsed.domain or parsed.replace or not validate_whm_connection(parsed): if not parsed.domain: print('Additional Information Required') parsed.domain = input( "Enter the primary domain for the cPanel you are transferring: " ) print('Running:') cmd = [ '/scripts/getremotecpmove', parsed.remote_host, parsed.user, parsed.domain, ] print('echo', shlex.quote(parsed.res_pass), '|', shlex.join(cmd)) backup = get_cpanel_backup(parsed) if backup: backup = set_backup_to_standard_naming(backup, parsed.user) if ( parsed.replace and check_user(parsed.replace) and backup is not False ): print( yellow(f'Removing Account {parsed.replace} so it can '), yellow('be restored from the backup'), ) subprocess.check_call( ['/scripts/removeacct', '--force', parsed.user] ) if not parsed.norestore: restore_backup(backup, parsed.user, parsed) else: parsed.userinfo = remote_whm_post( parsed, 'json-api/listaccts', { 'api.version': 1, 'want': 'user,disklimit,diskused,domain,suspended', }, ) # Check to see if the WHM connection failed and if so switch # to single transfer mode if parsed.userinfo: try: print(parsed.userinfo['cpanelresult']['data']['reason']) # Check for a read-only filesystem, and exit if found if "Read-only" in parsed.userinfo['metadata']['reason']: sys.exit('Read-only file system found') if prompt_y_n( 'Connection to remote WHM Failed. Would you like to try ' 'connecting via cPanel? Select Yes if not a reseller.' ): if not parsed.domain: print('Additional Information Required') parsed.domain = input( "Enter the primary domain for the cPanel " "you are transferring: " ) backup = get_cpanel_backup(parsed) if backup: backup = set_backup_to_standard_naming( backup, parsed.user ) if not parsed.norestore: restore_backup(backup, parsed.user, parsed) except Exception as exc: print(exc, file=sys.stderr) # This handles the case of remote_whm_post returning False, and was # otherwise not a caught exception prior else: sys.exit( 'There was an issue connecting to the remote host. Exiting.' ) userdata = {} pickerlist = [] toobig = [] try: for userinfo in parsed.userinfo['data']['acct']: userdata[userinfo['user']] = {} userdata[userinfo['user']]['domain'] = userinfo['domain'] userdata[userinfo['user']]['diskused'] = userinfo['diskused'] userdata[userinfo['user']]['disklimit'] = userinfo['disklimit'] userdata[userinfo['user']]['disklimit'] = userinfo['suspended'] if ( str(userinfo['diskused'].split('M')).lower() == 'none' or parsed.nosize or int(userinfo['diskused'].split('M')[0]) <= 6000 ): pickerlist.append( '{} - {} - {}'.format( userinfo['user'], userinfo['domain'], userinfo['diskused'], )[:50] ) if ( not parsed.nosize and int(userinfo['diskused'].split('M')[0]) >= 6000 ): toobig.append(userinfo['user']) if userinfo['suspended'] == 1: try: print( red(f'Unable to transfer {userinfo["user"]}'), red('because it is suspended.'), ) pickerlist.remove( '{} - {} - {}'.format( userinfo['user'], userinfo['domain'], userinfo['diskused'], )[:50] ) except Exception: pass except KeyError: print(userdata) sys.exit('Unable to gather required information') if toobig: print( 'The Following Accounts are too big to move by default.', 'You can use --nosize if you need to transfer them', ) print(*toobig) if not prompt_y_n('Would you like to continue?'): sys.exit('Try again with --nosize to ignore 6GB limit') if parsed.all: opts = [i.split(' ')[0] for i in pickerlist] else: useroptions = [i.split(' ')[0] for i in opts] print("Select users to transfer") print(opts.join("\n")) print("send q to finish") opts = [] while True: user_input = input().lower() if user_input in useroptions and user_input not in opts: opts.append(user_input) elif user_input == "q": break split = user_input.split(" ") if len(split) > 1: for name in split: if name in useroptions and name not in opts: opts.append(name) os.system('clear') print(opts) sys.stdout.flush() status = {} for user in opts: status[user] = 'Pending' for user in opts: print_status_info(status) try: if ( userdata[user]['disklimit'].lower() != 'unlimited' and str(userdata[user]['diskused'].split('M')).lower() != 'none' ): if ( int(userdata[user]['diskused'].split('M')[0]) * 2 ) < userdata[user]['disklimit'].split('M')[0]: print( red(f'Warning: User {user}: Does not have double'), red('the quota of the disk space used which can '), red('cause problems'), ) print( green('Setting Quota to unlimited to ensure no '), green(f'problem with backup on {user}'), ) newquota = remote_whm_post( parsed, 'json-api/modifyacct', {'api.version': 1, 'user': user, 'QUOTA': 0}, ) if newquota['metadata']['result'] == 1: print(green('Result: Success')) except Exception as exc: print(exc) print(red(f'Failed to set new Quota on {user}')) backup = get_cpanel_backup( parsed, restoreuser=user, restoredomain=userdata[user]['domain'] ) if backup and not parsed.norestore: backup = set_backup_to_standard_naming(backup, user) status[user] = restore_backup(backup, user, parsed) elif parsed.norestore: status[user] = 'Restore Skipped' else: status[user] = 'Failed' print_status_info(status) print('Done') def print_status_info(status): tbl = PrettyTable() tbl.field_names = ["User", "Status", "Conflict Info"] for user, itemstatus in status.items(): if '-' in status: conflict = status.split('-')[1:] itemstatus = status.split('-')[0] else: conflict = None tbl.add_row([user, itemstatus, conflict]) print(tbl) if __name__ == "__main__": main()