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/reset_email |
#!/opt/imh-python/bin/python3 """Resets email passwords and sends an email notification""" from platform import node from datetime import datetime import argparse # for Input selection import random import string # for random import logging import sys import subprocess from os import urandom from pathlib import Path import json from cpapis import cpapi2 import rads from rads.color import red, green import pp_api run = lambda x: subprocess.check_output( x, encoding='UTF-8', errors='replace' ).splitlines() def domain_owner(domain): """Obtain the cPanel use who owns a provided domain""" try: owner = run(['/scripts/whoowns', domain]) except Exception: error = f"Error running whoowns on {domain}" print(red(error)) sys.exit(1) return owner def gather_email_accounts(user_to_parse, reset_all): """Get a list of usernames or an individual cPanel user""" email_list = [] # pull list of email accounts from cPanel pops = cpapi2(module='Email::listpopssingle', user=user_to_parse) for entry in pops['cpanelresult']['data']: email_list.append(entry['login']) if reset_all: # If the option to reset all emails was selected picker_opts = email_list else: # Reset only the specific accounts selected picker_opts = picker_menu(email_list) return picker_opts def picker_menu(email_list): """Show a menu allowing the user to select from a list of email addresses""" opts = [] print("Select email to reset") print("\n".join(opts)) print("send q to finish") while True: user_input = input().lower() if user_input in email_list 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 email_list and name not in opts: opts.append(name) return opts def parse_args(): """Parse commandline arguments with argparse""" parser = argparse.ArgumentParser() # The Email and User options are mutually-exclusive usergroup = parser.add_mutually_exclusive_group() usergroup.add_argument( '-u', '-user', action='store', dest='User', help='Define your User' ) usergroup.add_argument( '-e', '-email', nargs='+', dest='Emails', help='Set email to reset' ) parser.add_argument( '-m', '-message', action='store', dest='Message', help='Reason why the password was reset', ) parser.add_argument( '-a', '-all', action='store_true', dest='UserAll', help='Reset all USER\'s email accounts', ) parser.add_argument( '-c', '-clear', action='store_true', dest='Clear', help='Search exim queue and kill from user', ) results = parser.parse_args() # If neither User nor Email selected if not results.User and not results.Emails: print(red('No user or email defined')) sys.exit(1) # Confirm User is actual cPanel user if results.User: if not rads.is_cpuser(results.User): print(red('Not a valid user')) sys.exit(1) if not results.Message: sys.exit('Use the -m flag to add a message') return results def prelim_checks(user): """ Function to check for bulk mail exemptions via /home/$user/.senderlimit and /home/$user/.imh/.exim_bulk """ senderlimit_path = f'/home/{user}/.senderlimit' exim_bulk_path = f'/home/{user}/.imh/.exim_bulk' consider_flag = True print("Checking for bulk mail exemptions...") if Path(senderlimit_path).is_file(): # Grab text from senderlimit file with open(senderlimit_path, encoding='ascii') as senderlimit_file: senderlimit = senderlimit_file.read().strip() # Push warning in case of limit over 250 and prompt # whether to continue or not if int(senderlimit) > 250: consider_flag = False answer = rads.prompt_y_n( f"Senderlimit for {user} is set to {senderlimit}, " "do you wish to proceed with resetting password?" ) if not answer: sys.exit(1) if Path(exim_bulk_path).is_file(): # System appears to generate a blank file for # /home/$user/.imh/.exim_bulk, we just need to see if it exists answer = rads.prompt_y_n( "Exim bulk limit file exists, please check PowerPanel for more " "information. Do you wish to proceed with resetting password? " ) consider_flag = False if not answer: sys.exit(1) # If neither warning appeared, continue without prompting. if consider_flag: print( "No bulk mail conflicts to consider. ", f"Checked {senderlimit_path} and {exim_bulk_path}", ) def main(): """Main program logic""" rads.setup_logging( path='/var/log/suspension.log', loglevel=logging.INFO, print_out=False ) args = parse_args() userdict = {} if args.User: # Get email accounts to reset # dict formation: {'userna5': ['email2', 'email2']} userdict[args.User] = gather_email_accounts(args.User, args.UserAll) if args.Emails: for email in args.Emails: # initialize domain = owner = None # parse domain for each email and find the owner domain = email.split('@')[1] owner = domain_owner(domain)[0] if len(owner) < 1: print(f"Failed to find owner for {domain}") sys.exit(1) else: if owner in userdict: userdict[owner].append(email) else: userdict[owner] = [email] valid_emails = [] prelim_checks(owner) # Get list of emails owned by found owners for owner, emails in userdict.items(): for entry in cpapi2(module='Email::listpopssingle', user=owner)[ 'cpanelresult' ]['data']: valid_emails.append(entry['login']) email_to_reset_valid = [] # Ensure each email actually exists in owner's account for email in emails: if email in valid_emails: email_to_reset_valid.append(email) else: print( "{} does not exist in {}'s account.".format( email, owner ) ) # reset userdict with only the actual email accounts userdict[owner] = email_to_reset_valid # send the processed userdict to reset_email_with_api reset_email_with_api(userdict, args.Message, args.Clear) def reset_email_with_api(userdict, reason, clear): """Resets a list of eail passwords and sends a notification""" length = 18 chars = string.ascii_letters + string.digits + '!#$%^&*()_+{}?><' if 'inmotionhosting' in node() or 'servconfig' in node(): eid = 617 elif 'webhostinghub' in node(): eid = 618 else: sys.exit('Cannot find brand to connect to AMP. Check the hostname.') for owner, emails in userdict.items(): msg = "" for email in emails: # check and update password reset history resets = check_email_records(email, owner) # generate new password random.seed = urandom(1024) newpass = ''.join(random.choice(chars) for _ in range(length)) print("---") print(f"{email} new password: {newpass}") emaildomain = email.split('@')[1] emailuser = email.split('@')[0] # reset password with cPanel api try: result = cpapi2( module='Email::passwdpop', user=owner, args={ "domain": emaildomain, "email": emailuser, "password": newpass, }, ) if result['cpanelresult']['data'][0]['result'] == 1: print(green('Success')) except Exception as exc: print(red(f"Failed to reset password. Debug info: {exc}")) # if resets > 2, we've done this before. # Time to review the accounts if resets > 3: msg = ( "[!!!] {} has been reset multiple times. " "Please check {}'s account for a final notice " "and suspend if necessary.".format(email, owner) ) print(red(msg)) elif resets > 2: msg = ( "[!!] {} has been reset more than twice, " "please send final notice.".format(email) ) print(red(msg)) # if msg contains a warning, give T2S a link to review the account if len(msg) >= 1: print( "Please review account: " "http://gm1.imhadmin.net/cgi-bin/ppanel-search.cgi?query=" "{}".format(owner) ) print(f"Now emailing {owner}...") message = { 'subject': "Email Password Reset", 'eid': eid, 'var1': reason, 'var2': emails, } mail_users(message, owner) # clear if we opted to clear the queue, warn if not if clear: clear_queue(emails) if not clear: print("-c not used. Please clear the queue.") print(green("--- Resets complete!")) def mail_users(message, cpuser): """This part sends the message to the user. Reference for pp_api emails""" # check to see if we need to tattle to a reseller about their child if 'servconfig' in node(): owner = rads.get_owner(cpuser) if owner not in rads.OUR_RESELLERS: cpuser = owner pp_connect = pp_api.PowerPanel() template_id = int(message['eid']) data = { 'template': template_id, 'cpanelUser': cpuser, 'variable_1': message['var1'], 'variable_2': ', '.join(message['var2']), 'variable_3': None, } results = pp_connect.call("notification.send", **data) if results.status != 0: log_error = f"Failed to send notification to {cpuser}" logging.error(log_error) print("{} {}".format(vars(results)['message'], cpuser)) print(red("Failed to get a valid response from AMP.")) else: log_success = "Success: {} sent to {} by {}".format( message['subject'], cpuser, rads.get_login() ) logging.info(log_success) print("Message Sent Successfully") def search_exim_queue(kill_email, all_messages): """Collect the list of outgoing emails in queue to be killed""" killcount = 0 print(green(f"Killing messages in queue from {kill_email}")) for email_id in all_messages: try: header = run(['/usr/sbin/exim', '-Mvh', email_id]) except subprocess.CalledProcessError: continue for line in header: if line.startswith('--auth_id'): if kill_email in line: killcount = kill_from_queue(email_id, killcount) break if killcount > 0: print(f'Successfully killed {killcount} messages') def clear_queue(email_list): """Collect the list of bouncebacks resulting from the spam to be removed from the queue.""" email_killed = 0 all_messages = run(['/usr/sbin/exiqgrep', '-if', '".*"']) for email in email_list: search_exim_queue(email, all_messages) kill_bounce_list = run(['/usr/sbin/exiqgrep', '-if', '<>', '-r', email]) print(green(f"Killing bouncebacks in queue from {email}")) for email_id in kill_bounce_list: email_killed = kill_from_queue(email_id, email_killed) if email_killed > 0: print(f"Bouncebacks killed from queue {email_killed}") def kill_from_queue(email_id, kill_count): """Given an email id, exim -Mrm id""" if email_id: try: run(['/usr/sbin/exim', '-Mrm', email_id]) kill_count += 1 except subprocess.CalledProcessError: return kill_count return kill_count def check_email_records(email_account, account_owner): """ Checking the amount of times an account has had its password reset within 6 months. :param email_account: Email account in question. :param account_owner: The owner of the email account. :return: Number showing how many times the account has had its password reset. """ path = Path('/home', account_owner, '.imh/reset_email.json') path.parent.mkdir(exist_ok=True, mode=0o755) # Loading the data. try: with open(path, encoding='ascii') as fname: data = json.loads(fname.read()) except (OSError, ValueError): data = {} print(green(f"Creating {path}")) if email_account not in list(data.keys()): data[email_account] = [str(datetime.now())] with open(path, "w", encoding='ascii') as fname: fname.write(json.dumps(data)) return 0 # Updating the list now; before we count. data[email_account].append(str(datetime.now())) with open(path, "w", encoding='ascii') as fname: fname.write(json.dumps(data)) # String to expect: ['2018-11-12 13:49:47.212776'] count = 0 for date in data[email_account]: now = datetime.now() time = datetime.strptime(date, "%Y-%m-%d %H:%M:%S.%f") if abs(now - time).days <= 180: count += 1 return count if __name__ == "__main__": main()