import os from pkg_resources import resource_filename, cleanup_resources, resource_string import atexit from pathlib import Path atexit.register(cleanup_resources, force=True) os.environ["KRB5_CONFIG"] = str( Path(resource_filename(__name__, "krb5.conf")).resolve() ) import kadmin import getpass import argparse import secrets import string import shutil import logging from ldap3 import Server, Connection, LEVEL from ldap3.utils.conv import escape_filter_chars from ldap3.utils.dn import escape_rdn import subprocess import smtplib from jinja2 import Template from email.message import EmailMessage FROM_LDAP = object() parser = argparse.ArgumentParser() parser.add_argument("--admin", default=getpass.getuser()) parser.add_argument("--verbose", action="store_true") subparsers = parser.add_subparsers(dest="cmd", help="command") reset_password = subparsers.add_parser( "reset_password", help="change user password to newly generated one and send it to his email address from LDAP", ) reset_password.add_argument("user") reset_password.add_argument( "--show-password", action="store_true", help="print generated password" ) reset_password.add_argument("email_address", default=FROM_LDAP, nargs="?") APG_CMD = shutil.which("apg") def generage_password(length=15): if APG_CMD is None: logging.warning("apg command not found. Using built in password generator") pool = string.ascii_lowercase + string.ascii_uppercase + string.digits password = "".join([secrets.choice(pool) for _ in range(length)]) else: password = ( subprocess.run( [APG_CMD, "-m", str(length), "-n", "1", "-M", "NCL"], check=True, capture_output=True, ) .stdout.decode() .strip() ) if len(password) != length: raise Exception("Password generation failed") return password def main(): args = parser.parse_args() logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO) if args.cmd == "reset_password": admin_pass = getpass.getpass(f"{args.admin}@HACKERSPACE.PL password: ") logging.debug("initializing kadmin") k = kadmin.init_with_password(f"{args.admin}/admin", admin_pass) logging.debug("kadmin initialized") user = args.user if user is None: user = input("User: ") p = k.get_principal(user) password = generage_password() if args.email_address is not FROM_LDAP: address = args.email_address else: address = get_email_address(args.admin, admin_pass, user) if args.show_password: print(f'password: "{password}"') i = input( f"Type yes to reset {user}'s password and send email to {address}\n" ).strip() if i != "yes": print("Aborted") return p.change_password(password) print("password changed") send_mail(args.admin, admin_pass, password, user, address) print("email sent") else: parser.print_help() def get_email_address(admin, admin_pass, uid): logging.debug("fetching email address from LDAP") s = Server("ldap.hackerspace.pl", use_ssl=True) with Connection( s, user=f"uid={escape_rdn(admin)},ou=People,dc=hackerspace,dc=pl", password=admin_pass, raise_exceptions=True, ) as c: logging.debug("connected to LDAP server") c.search( search_base="ou=People,dc=hackerspace,dc=pl", search_filter=f"(uid={escape_filter_chars(uid)})", search_scope=LEVEL, attributes=["mailRoutingAddress"], ) if not c.entries: raise Exception("empty response") if len(c.entries) > 1: raise Exception("too many responses") address = c.entries[0]["mailRoutingAddress"] logging.debug(f"got mail address from LDAP: {address}") return address def send_mail(admin, admin_password, password, user, address): mail_template = Template( resource_string(__name__, "password_reset.jinja2").decode() ) msg = EmailMessage() config = {"password": password, "user": user, "admin": admin} msg.set_content(mail_template.render(config)) msg["Subject"] = f"Password reset for {user}@hackerspace.pl" msg["From"] = f"{admin}@hackerspace.pl" msg["To"] = address with smtplib.SMTP_SSL("mail.hackerspace.pl") as s: s.login(admin, admin_password) s.send_message(msg)