# {{ ansible_managed | comment }}

from subprocess import check_call, CalledProcessError, run
import argparse
import configparser
import ldap
#import os.path
from pathlib import Path

''' T241113 useradmin_more_tasks
  References:
  python-ldap docs https://www.python-ldap.org/en/python-ldap-3.4.3/index.html
  python-argparse https://docs.python.org/3/library/argparse.html#
'''


# #### begin of helper functions ####
def useradmin_getldappass():
    # get ldap access password from init file:
    # Never pass secrets on commandline.
    # They will be globally available via /proc.
    config = configparser.ConfigParser()
    # file contains: [ldap]\nldap_useradmin_pass: the_secret-password
    config.read('/etc/perfact/ldap_useradmin_pass.ini')
    try:
        ldappassword = config.get("ldap", "ldap_useradmin_pass")
    except FileNotFoundError as error:
        print(
            'cannot read password form ',
            '/etc/perfact/ldap_useradmin_pass.ini ',
            '(Hint: must be run with sudo)'
        )
        raise error
    # print("ldap_useradmin_pass=",ldap_useradmin_pass,"\n")
    return ldappassword


def ldap_connect():
    # initialize and return ldap object:
    ldap_useradmin_pass = useradmin_getldappass()
    # connection to local api socket (ldapi:///), see /etc/default/slapd
    myldap = ldap.initialize("ldap://")
    myldap.simple_bind_s(
        "cn=perfact-useradmin,ou=Manager,dc=perfact,dc=de",
        ldap_useradmin_pass
    )
    return myldap


def useradmin_ldap_search(mybase, myfilter, myelements):
    myldap = ldap_connect()
    res = myldap.search_s(mybase, ldap.SCOPE_SUBTREE, myfilter, myelements)
    myldap.unbind()
    return(res)


def useradmin_nextuid(mysearchbase):
    # get the next free uidNumber
    # arg is 'mail' or 'file'
    # search in objectclass=posixaccount

    result = useradmin_ldap_search(
        f'ou={mysearchbase},dc=perfact,dc=de',
        'objectclass=posixaccount',
        ['uidNumber'],
    )

    uidNum = 0
    for a in result:
        uidNumtemp = int(a[1].get('uidNumber')[0])
        if uidNumtemp > uidNum:
            uidNum = uidNumtemp
    nextNum = int(uidNum) + 1
    return str(nextNum)

def useradmin_nextgui(mysearchbase):
    # get the next free gidNumber
    # arg is 'mail' or 'file'
    # search in objectclass=posixgroup

    result = useradmin_ldap_search(
        f'ou={mysearchbase},dc=perfact,dc=de',
        'objectclass=posixgroup',
        ['gidNumber'],
    )

    gidNum = 0
    for a in result:
        gidNumtemp = int(a[1].get('gidNumber')[0])
        if gidNumtemp > gidNum:
            gidNum = gidNumtemp
    gnextNum = int(gidNum) + 1
    return str(gnextNum)


def useradmin_slappasswd(mypassword):
    try:
        myslapwasswd = run(
            [
                '/usr/sbin/slappasswd',
                '-s',
                mypassword,
            ],
            capture_output=True
        ).stdout
    except CalledProcessError as error:
        print(
            'Error when running command:',
            '/usr/sbin/slappasswd',
            '-s',
            'PASSWORD',
        )
        raise error

    # remove trailing '\n'
    return myslapwasswd.decode('utf-8').strip()

# #### end of helper functions ####


# ### commands for perfact-login (olpg) ###

''' even though passing just '(args)' is a bad habbit, we decided to do
    exactly this.
    We are accepting all args und let argparse do the rest.
    Especially we let argparse output errors and help regarding the given
    parameters. '''


def create_signatures(args):
    '''
    Here we only need to call the program.
    Files are owned by perfact-useradmin and changes are made by Ansible.
    '''
    # parse args, only used for a help message in this case
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py create_signatures',
        description='Runs create_signatures.py',
        add_help=True,
    )
    parser.add_argument(
        '--user', type=str,
        help='user name',
        required=False,
    )
    myargs = parser.parse_args(args)

    mycall = [
        'python3',
        '/opt/perfact/create_signatures/create_signatures.py',
    ]

    if myargs.user != None:
        mycall.append('--user')
        mycall.append(myargs.user)

    try:
        check_call(
            mycall,
            cwd='/opt/perfact/create_signatures/'
        )
    except CalledProcessError as error:
        print(
            "Error when running command ",
            mycall
        )
        raise error


def renew_pf_verbindungskonfiguration(args):
    '''
    Tasks:
        Update repository from main.
        Call programm to deploy verbindungskonfiguration to users.
    Hint:
        Files are owned by perfact-useradmin and changes come from
        git repository.
    '''
    # parse args, only used for a help message in this case
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py renew_pf_verbindungskonfiguration',
        description=(
            'Updates repository and runs '
            'pf_verbindungskonfiguration/do_all.sh'
        ),
        add_help=True,
    )
    parser.parse_args(args)

    try:
        check_call(
            [
                '/bin/bash',
                # call bash script because quotation with check_call
                # did not work
                # TODO: apply solution from Lea here and git rif of the
                # bash script ... Prio 100, SK
                '/opt/perfact/custom/update_pf_verbindungskonfiguration.sh',
            ],
        )
    except CalledProcessError as error:
        print(
            "Error when running command ",
            "'update_pf_verbindungskonfiguration.sh'"
        )
        raise error

    try:
        check_call(
            [
                '/bin/bash',
                '/opt/perfact/pf_verbindungskonfiguration/do_all.sh',
            ],
            cwd='/opt/perfact/pf_verbindungskonfiguration/'
        )
    except CalledProcessError as error:
        print(
            "Error when running command "
            "'/bin/bash "
            "/opt/perfact/pf_verbindungskonfiguration/do_all.sh'"
        )
        raise error

### Helper functions for mail accounts
# helper function: return a list of users mail-aliases (mailacceptinggeneralid)
def get_list_of_user_aliases(thisuser):
    searchbase = "uid=" + thisuser + ",ou=users,ou=mail,dc=perfact,dc=de"
    user_mail_addresses = useradmin_ldap_search(
        searchbase,
        "objectClass=top",
        ["mailacceptinggeneralid"])

    if not user_mail_addresses:
        # return empty list
        return []

    ''' convert this to list:
    [('uid=stestmailaccount,ou=users,ou=mail,dc=perfact,dc=de',
        {
            'mailacceptinggeneralid': [
                b'stest@perfact.de', b'stest2@perfact.de',
                ]
        }
    )]'''
    useraliases = [
        item.decode('utf-8')
        for item in user_mail_addresses[0][1]['mailacceptinggeneralid']
    ]
    return useraliases


# helper function get all distribution lists containing mailadress
def get_distribution_lists_of(mailaddress):
    # print('debug: - looking for address: '+mailaddress)
    user_mail_aliases = useradmin_ldap_search(
        "ou=mailaliases,ou=mail,dc=perfact,dc=de",
        "(&(objectClass=nisMailAlias)" +
        f"(rfc822MailMember={mailaddress}))",
        ["cn"])

    if not user_mail_aliases:
        # return empty list
        return []

    ''' convert this list of tuples witch dicts to list of strings:
    [
    ('cn=vertrieb,ou=mailaliases,ou=mail,dc=perfact,dc=de',
        {'cn': [b'vertrieb']}),
    ('cn=liste2,ou=mailaliases,ou=mail,dc=perfact,dc=de', {'cn': [b'liste2']})
    ]'''
    common_names = [
        item[1]['cn'][0].decode('utf-8')
        for item in user_mail_aliases
    ]
    return common_names


# helper fuction remove_user_aliases
def remove_user_aliases(thisuser):
    # remove all mail-addresses of user from all distribution list
    user_mail_addresses = get_list_of_user_aliases(thisuser)
    # print('debug: user_mail_addresses='+str(user_mail_addresses))
    if not user_mail_addresses:
        print("User '+thisuser+' has no mail addresses - nothing to remove")
        return

    # loop over mailaliases (mailacceptinggeneralid):
    for address in user_mail_addresses:
        # shorten address to prefix:
        prefix = address.split('@')[0]

        # check all possible mail-addresses for prefix:
        for suffix in ['', '@perfact.de', '@perfact-innovation.de']:
            distributions_lists = get_distribution_lists_of(prefix+suffix)

            # if distributions_lists not empty
            if not distributions_lists:
                print('no distribution_list for '+prefix+suffix)
                # finish for loop now
                continue
            for thislist in distributions_lists:
                # print('debug: removing from list: '+
                #     thislist+' member: '+prefix+suffix)
                mail_del_from_alias(["--alias", thislist,
                                    "--target", prefix+suffix])


# ### commands for mailaccounts ###
# ## mailserver aliases ##
def mail_add_alias(args):
    '''
    Tasks:
        Add alias to ldap ou=mailaliases,ou=mail,dc=perfact,dc=de
        This will:
        1. create ldif
        2. ldapadd new_ldif
        Parameter: alias=
        Parameter: targets=comma seperated mail addresses

        dn: cn=ALIAS,ou=mailaliases,ou=mail,dc=perfact,dc=de
        objectClass: nisMailAlias
        cn: ALIAS
        rfc822MailMember: mmustermann
        rfc822MailMember: bmusterfrau
        rfc822MailMember: [more users]
        rfc822MailMember: user@other_domain.tld
    '''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py mail-add-alias',
        description=('Add new mail alias to ldap master'),
        add_help=True,
    )
    parser.add_argument('--alias', type=str, help='alias name', required=True)
    parser.add_argument(
        '--targets', type=str,
        help="comma seperated list of destinations", required=True
    )
    parser.add_argument("-g", "--general", action="store_true")
    parser.add_argument("-s", "--special", action="store_true")
    myargs = parser.parse_args(args)

    alias_name = myargs.alias
    alias_targets = myargs.targets.split(",")

    dn = "cn=" + alias_name + ",ou=mailaliases,ou=mail,dc=perfact,dc=de"
    modlist = [
        ("objectClass", "nisMailAlias".encode("utf-8")),
        ("cn", alias_name.encode("utf-8")),
        ("rfc822MailMember", [t.encode("utf-8") for t in alias_targets])
    ]

    myldap = ldap_connect()
    myldap.add_s(dn, modlist)
    myldap.unbind()
    print('Added mail alias ' + alias_name + ' to ldap.')


def mail_del_alias(args):
    '''
    Tasks:
        Remove alias from ldap
        Parameter: alias=
    '''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py mail-del-alias',
        description=('Removes alias from ldap master'),
        add_help=True,
    )
    parser.add_argument('--alias', type=str, help='alias name', required=True)
    myargs = parser.parse_args(args)

    dn = "cn=" + myargs.alias + ",ou=mailaliases,ou=mail,dc=perfact,dc=de"

    myldap = ldap_connect()
    myldap.delete_s(dn)
    myldap.unbind()
    print('Deleted alias ' + myargs.alias)


def mail_show_alias(args):
    '''
        user=

        This will:
        Show alias'''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py mail-show-alias',
        description=('List this alias'),
        add_help=True,
    )
    parser.add_argument(
        '--alias', type=str,
        help='Mail alias (distribution list)',
        required=True,
    )
    myargs = parser.parse_args(args)

    alias_list = useradmin_ldap_search(
        'ou=mailaliases,ou=mail,dc=perfact,dc=de',
        f"cn={myargs.alias}",
        ["rfc822MailMember"])

    print(f"{myargs.alias}:",end=" ")
    for alias in alias_list:
        for target in alias[1]["rfc822MailMember"]:
            print(target.decode('utf-8'),end=", ")
        print()


def mail_show_aliases(args):
    '''
        user=

        This will:
        List all aisases for this user'''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py mail-show-aliases',
        description=('List all aliases'),
        add_help=True,
    )
    myargs = parser.parse_args(args)

    alias_list = useradmin_ldap_search(
        'ou=mailaliases,ou=mail,dc=perfact,dc=de',
        f"objectClass=nisMailAlias",
        ["cn","rfc822MailMember"])

    ''' alias_list is:
    [
        ('cn=hostmaster,ou=mailaliases,ou=mail,dc=perfact,dc=de',
        {
            'cn': [b'hostmaster'],
            'rfc822MailMember': [b'sla']}),
        ('cn=usenet,ou=mailaliases,ou=mail,dc=perfact,dc=de',
        {
            'cn': [b'usenet'],
            'rfc822MailMember': [b'sla']}),
    '''

    for alias in alias_list:
        alias_name = alias[1]["cn"][0]
        print(f"{alias_name.decode('utf-8')}:", end=" ")
        for target in alias[1]["rfc822MailMember"]:
            print(target.decode('utf-8'),end=", ")
        print()


def mail_show_aliases_for_user(args):
    '''
        user=

        This will:
        List all aisases for this user'''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py mail-show-aliases-for-user',
        description=('List all aliases for user'),
        add_help=True,
    )
    parser.add_argument(
        '--user', type=str,
        help='mail user name',
        required=True,
    )
    myargs = parser.parse_args(args)

    user_mail_addresses = get_list_of_user_aliases(myargs.user)
    print('User '+myargs.user+' is in following aliases (distribution lists):')
    # loop over mailaliases (mailacceptinggeneralid):
    for address in user_mail_addresses:
        # shorten address to prefix:
        prefix = address.split('@')[0]

        # check all possible mail-addresses for prefix:
        for suffix in ['', '@perfact.de', '@perfact-innovation.de']:
            distributions_lists = get_distribution_lists_of(prefix+suffix)
            if distributions_lists:
                print(f"Mailadress {prefix+suffix}:",end=" ")
                for thislist in distributions_lists:
                    print(thislist,end=", ")
                print()


# ## add other mailaddress (without user) ##
def add_other_mailaccount(args):
    '''
    Tasks:
        Add account to ldap ou=users,ou=mail,dc=perfact,dc=de
        This will:
        1. create ldif
        2. ldapadd new_ldif
        Parameter: account=
        Parameter: passwd=

        Example result in ldap will be:
        dn: uid=fritzbox-um,ou=users,ou=mail,dc=perfact,dc=de
        objectClass: top
        objectClass: inetOrgPerson
        objectClass: posixAccount
        objectClass: organizationalPerson
        objectClass: shadowAccount
        objectClass: postfixUser
        cn: fritzbox-um PerFact_Mailaccount
        uid: fritzbox-um
        uidNumber: 1417
        gidNumber: 1417
        homeDirectory: /home/fritzbox-um
        loginShell: /bin/false
        userPassword:: [ldap-encrypted-password]
        mailacceptinggeneralid: fritzbox-um@perfact.de
        maildrop: fritzbox-um
        mail: fritzbox-um@perfact.de
        sn: fritzbox-um
    '''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py add-other-mailaccount',
        description=('Add other mailaccount without user to ldap master'),
        add_help=True,
    )
    parser.add_argument(
        '--account', type=str,
        help='mail-prefix',
        required=True,
    )
    parser.add_argument(
        '--password', type=str,
        help="password for mailaccount",
        required=True,
    )
    myargs = parser.parse_args(args)

    if "@" in myargs.account:
        print("Error: account name cannot contain @")
        exit(1)

    # create slapppasswd
    # Passwords are not stored in plaintext. slappasswd will crypt them.
    slappassword = useradmin_slappasswd(myargs.password)

    next_number = useradmin_nextuid("mail")
    account_name = myargs.account
    account_mail = f"{account_name}@perfact.de"

    dn = "uid=" + account_name + ",ou=users,ou=mail,dc=perfact,dc=de"
    modlist = [
        ("objectClass",
            [b"top",
             b"inetOrgPerson",
             b"posixAccount",
             b"organizationalPerson",
             b"shadowAccount",
             b"postfixUser"]),
        ("cn", f"{account_name} PerFact_Mailaccount".encode("utf-8")),
        ("uidNumber", next_number.encode("utf-8")),
        ("gidNumber", next_number.encode("utf-8")),
        ("homeDirectory", f"/home/{account_name}".encode("utf-8")),
        ("loginShell", b"/bin/false"),
        ("userPassword", slappassword.encode("utf-8")),
        ("mailacceptinggeneralid", account_mail.encode("utf-8")),
        ("maildrop", account_name.encode("utf-8")),
        ("mail", f"{account_name}@perfact.de".encode("utf-8")),
        ("sn", account_name.encode("utf-8"))
    ]

    myldap = ldap_connect()
    myldap.add_s(dn, modlist)
    myldap.unbind()
    print('Added other mail account to ldap: '+dn)


def del_other_mailaccount(args):
    '''
    Tasks:
        Delete account from ldap ou=users,ou=mail,dc=perfact,dc=de
        Parameter: account=
    '''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py add-other-mailaccount',
        description=('Add other mailaccount without user to ldap master'),
        add_help=True,
    )
    parser.add_argument(
        '--account', type=str,
        help='mail-prefix',
        required=True,
    )
    myargs = parser.parse_args(args)

    if "@" in myargs.account:
        print("Error: account name cannot contain @")
        exit(1)

    dn = "uid=" + myargs.account + ",ou=users,ou=mail,dc=perfact,dc=de"
    myldap = ldap_connect()
    myldap.delete_s(dn)
    myldap.unbind()
    print('Deleted other mail account from ldap: '+dn)


def mail_add_user(args):
    '''
        user=
        password=
        mailaddress=
        lastname=
        virtuals=comma separeted list of virtual aliases *optional

    Example result in ldap will be:
    dn: uid=LOGINNAME,ou=users,ou=mail,dc=perfact,dc=de
    objectClass: top
    objectClass: inetOrgPerson
    objectClass: posixAccount
    objectClass: organizationalPerson
    objectClass: shadowAccount
    objectClass: postfixUser
    cn: LASTNAME
    uid: LOGINNAME
    uidNumber: NEXT_FREE (use function above)
    gidNumber: NEXT_FREE
    homeDirectory: /home/LOGINNAME
    loginShell: /bin/false
    userPassword:: CRYPTEDPASS
    mail: MAILADRESS
    maildrop: LOGINNAME
    mailacceptinggeneralid: 1. virtual address
    mailacceptinggeneralid: 2. virtual address ....
    sn: LASTNAME'''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py mail-add-user',
        description=('Add mail user to ldap master'),
        add_help=True,
    )
    parser.add_argument(
        '--user', type=str,
        help='mail user name',
        required=True,
    )
    parser.add_argument(
        '--password', type=str,
        help='mail user password',
        required=True,
    )
    parser.add_argument(
        '--mailaddress', type=str,
        help='mail user address',
        required=True,
    )
    parser.add_argument(
        '--lastname', type=str,
        help='mail user last name',
        required=True,
    )
    parser.add_argument(
        '--virtuals', type=str,
        help='comma separated list of virtual aliases *optional',
        required=False,
    )
    myargs = parser.parse_args(args)

    login_name = myargs.user
    last_name = myargs.lastname
    slappassword = useradmin_slappasswd(myargs.password)
    next_number = useradmin_nextuid("mail")
    mail_address = myargs.mailaddress
    mail_virtuals = myargs.virtuals

    dn = "uid=" + login_name + ",ou=users,ou=mail,dc=perfact,dc=de"
    modlist = [
        ("objectClass", [b"top",
                         b"inetOrgPerson",
                         b"posixAccount",
                         b"organizationalPerson",
                         b"shadowAccount",
                         b"postfixUser"]),
        ("cn", last_name.encode("utf-8")),
        ("uid", login_name.encode("utf-8")),
        ("uidNumber", next_number.encode("utf-8")),
        ("gidNumber", next_number.encode("utf-8")),
        ("homeDirectory", f"/home/{login_name}".encode("utf-8")),
        ("loginShell", b"/bin/false"),
        ("userPassword", slappassword.encode("utf-8")),
        ("mail", mail_address.encode("utf-8")),
        ("maildrop", login_name.encode("utf-8")),
        ("sn", last_name.encode("utf-8")),
        # always add the mail address to mailacceptinggeneralid
        ("mailacceptinggeneralid",  mail_address.encode("utf-8")),
    ]

    if mail_virtuals:
        modlist.extend([
            ("mailacceptinggeneralid", [v.encode("utf-8") for v
                                        in mail_virtuals.split(",")])]
        )

    myldap = ldap_connect()
    myldap.add_s(dn, modlist)
    myldap.unbind()
    print('Added user to ldap: ' + dn)


def mail_del_user(args):
    '''
        user=

        This will:
            ldapdelte dn: uid=LOGINNAME,ou=users,ou=mail,dc=perfact,dc=de

        and delete all occourencies of
        uid and
        uid@perfact.de and
        uid@perfact-innovation.de from aliaes

        and delete all mailacceptinggeneralid from aliases

        otherwise mails to this aliases will be bounced'''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py mail-del-user',
        description=('Delete mail user from ldap master'),
        add_help=True,
    )
    parser.add_argument(
        '--user', type=str,
        help='mail user name',
        required=True,
    )
    myargs = parser.parse_args(args)

    # fist remove user and all its aliases from all distribution lists:
    remove_user_aliases(myargs.user)

    # second remove user
    dn = "uid=" + myargs.user + ",ou=users,ou=mail,dc=perfact,dc=de"
    myldap = ldap_connect()
    myldap.delete_s(dn)
    myldap.unbind()
    print('User deleted from ldap: '+dn)


def mail_add_virtual_to_user(args):
    '''
        user=
        virtual=

        This will:
           ldapmodify add mailacceptinggeneralid to
                      uid=LOGINNAME,ou=users,ou=mail,dc=perfact,dc=de'''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py mail-add-virtual-to-user',
        description=('Add virtual mail address to user'),
        add_help=True,
    )
    parser.add_argument(
        '--user', type=str,
        help='mail user name',
        required=True,
    )
    parser.add_argument(
        '--virtual', type=str,
        help='mail virtual address',
        required=True,
    )
    myargs = parser.parse_args(args)

    virtual = myargs.virtual

    dn = "uid=" + myargs.user + ",ou=users,ou=mail,dc=perfact,dc=de"
    modlist = [
        (ldap.MOD_ADD, "mailacceptinggeneralid", virtual.encode("utf-8"))
    ]

    myldap = ldap_connect()
    myldap.modify_s(dn, modlist)
    myldap.unbind()
    print('Added virtual address '+virtual+' to user '+dn)


def mail_del_virtual_from_user(args):
    '''
        user=
        virtual=

        This will:
            ldapmodify remove mailacceptinggeneralid from
                       uid=LOGINNAME,ou=users,ou=mail,dc=perfact,dc=de '''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py mail-del-virtual-from-user',
        description=('Remove virtual mail address from user'),
        add_help=True,
    )
    parser.add_argument(
        '--user', type=str,
        help='mail user name',
        required=True,
    )
    parser.add_argument(
        '--virtual', type=str,
        help='mail virtual address',
        required=True,
    )
    myargs = parser.parse_args(args)

    virtual = myargs.virtual

    dn = "uid=" + myargs.user + ",ou=users,ou=mail,dc=perfact,dc=de"
    modlist = [
        (ldap.MOD_DELETE, "mailacceptinggeneralid", virtual.encode("utf-8"))
    ]

    myldap = ldap_connect()
    myldap.modify_s(dn, modlist)
    myldap.unbind()
    print('Removed virtual address '+virtual+' from user '+dn)


def mail_add_to_alias(args):
    '''
        alias=
        target=userlogin or mail-address

        This will:
            ldapmodify add rfc822MailMember to
                       cn=ALIASNAME,ou=mailaliases,ou=mail,dc=perfact,dc=de '''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py mail-add-virtual-to-user',
        description=('Add mail alias to user'),
        add_help=True,
    )
    parser.add_argument(
        '--alias', type=str,
        help='mail alias',
        required=True,
    )
    parser.add_argument(
        '--target', type=str,
        help='mail alias target',
        required=True,
    )
    myargs = parser.parse_args(args)

    dn = "cn=" + myargs.alias + ",ou=mailaliases,ou=mail,dc=perfact,dc=de"
    modlist = [
        (ldap.MOD_ADD, "rfc822MailMember", myargs.target.encode("utf-8"))
    ]

    myldap = ldap_connect()
    myldap.modify_s(dn, modlist)
    myldap.unbind()
    print('Alias '+dn+' extended with '+myargs.target)


def mail_del_from_alias(args):
    '''
        alias=
        target=userlogin or mail-address

        This will:
            ldapmodify remove rfc822MailMember from
                       cn=ALIASNAME,ou=mailaliases,ou=mail,dc=perfact,dc=de '''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py mail-add-virtual-to-user',
        description=('Remove alias from mail user'),
        add_help=True,
    )
    parser.add_argument(
        '--alias', type=str,
        help='mail alias',
        required=True,
    )
    parser.add_argument(
        '--target', type=str,
        help='mail alias target',
        required=True,
    )
    myargs = parser.parse_args(args)

    dn = "cn=" + myargs.alias + ",ou=mailaliases,ou=mail,dc=perfact,dc=de"
    modlist = [
        (ldap.MOD_DELETE, "rfc822MailMember", myargs.target.encode("utf-8"))
    ]

    myldap = ldap_connect()
    myldap.modify_s(dn, modlist)
    myldap.unbind()
    print('Removed from alias '+dn+' '+myargs.target)


def mail_set_passwd(args):
    '''
        user=
        password=

    This will: set user password in ldap:
    dn: uid=LOGINNAME,ou=users,ou=mail,dc=perfact,dc=de
    userPassword:: CRYPTEDPASS'''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py mail-set-passwd',
        description=('Set password of mailuser in ldap.'),
        add_help=True,
    )
    parser.add_argument(
        '--user', type=str,
        help='mail user name',
        required=True,
    )
    parser.add_argument(
        '--password', type=str,
        help='mail user password',
        required=True,
    )
    myargs = parser.parse_args(args)

    login_name = myargs.user
    slappassword = useradmin_slappasswd(myargs.password)

    dn = "uid=" + login_name + ",ou=users,ou=mail,dc=perfact,dc=de"
    modlist = [
        (ldap.MOD_REPLACE, "userPassword", slappassword.encode("utf-8")),
    ]
    myldap = ldap_connect()
    myldap.modify_s(dn, modlist)
    myldap.unbind()
    print('Set user password: ' + dn)


# ### commands for file accounts ###

# helper: get groups of user
def get_groups_of_user(thisuser):
    # return list of groups for user
    user_groups = useradmin_ldap_search(
        "ou=groups,ou=file,dc=perfact,dc=de",
        "(&(objectClass=posixGroup)" +
        f"(memberUid={thisuser}))",
        ["cn"])

    if not user_groups:
        return []

    ''' convert to list of strings:
    [
    ('cn=internalit,ou=groups,ou=file,dc=perfact,dc=de',
        {'cn': [b'internalit']}
    ),
    ('cn=logistics,ou=groups,ou=file,dc=perfact,dc=de',
        {'cn': [b'logistics']}
    ('cn=machinery,ou=groups,ou=file,dc=perfact,dc=de',
        {'cn': [b'machinery']}
    ]
    '''
    groups = [
        item[1]['cn'][0].decode('utf-8')
        for item in user_groups
    ]
    return groups


# helper function to return the complete dn from uid
def get_dn_from_uid(thisuser):
    dn = useradmin_ldap_search(
        "ou=users,ou=file,dc=perfact,dc=de",
        "(&(objectClass=posixAccount)" +
        f"(uid={thisuser}))",
        ["dn"])

    # [('cn=Anton Mustermann,ou=users,ou=file,dc=perfact,dc=de', {})]
    return dn[0][0]


# helper: verify user exists:
def file_user_exist(thisuser):
    if get_dn_from_uid(thisuser):
        return True
    else:
        return False


def file_add_user(args):
    '''
        user=
        password=
        lastname=
        firstname=
        givenname= OPTIONAL set custom givenName instead of FIRSTNAME

        Example result in ldap will be:
            dn: cn=FULLNAME,ou=users,ou=file,dc=perfact,dc=de
            objectClass: posixAccount
            objectClass: inetOrgPerson
            objectClass: organizationalPerson
            objectClass: person
            homeDirectory: /home/USER
            loginShell: /bin/bash
            uid: USER
            cn: FULLNAME
            uidNumber: NEXT_FREE_UID
            gidNumber: NEXT_FREE_UID
            sn: LASTNAME
            givenName: FIRSTNAME

    userdir will be created after adding user to ldap.
    '''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py file-add-user',
        description=('Add file user'),
        add_help=True,
    )
    parser.add_argument(
        '--user', type=str,
        help='file user name',
        required=True,
    )
    parser.add_argument(
        '--password', type=str,
        help='file user password',
        required=True,
    )
    parser.add_argument(
        '--firstname', type=str,
        help='file user first name',
        required=True,
    )
    parser.add_argument(
        '--lastname', type=str,
        help='file user last name',
        required=True,
    )
    parser.add_argument(
        '--givenname', type=str,
        help='file user given name',
        required=False,
    )
    myargs = parser.parse_args(args)

    next_number = useradmin_nextuid("file")
    slappassword = useradmin_slappasswd(myargs.password)
    login_name = myargs.user
    last_name = myargs.lastname
    first_name = myargs.firstname
    full_name = f"{first_name} {last_name}"
    if myargs.givenname:
        given_name = myargs.givenname
    else:
        given_name = myargs.firstname

    dn = "uid=" + myargs.user + ",ou=users,ou=file,dc=perfact,dc=de"
    modlist = [
        ("objectClass", [b"inetOrgPerson",
                         b"posixAccount",
                         b"organizationalPerson",
                         b"person"]),
        ("cn", full_name.encode("utf-8")),
        ("uid", login_name.encode("utf-8")),
        ("uidNumber", next_number.encode("utf-8")),
        # always add user to groups perfactusers=10000
        ("gidNumber", '10000'.encode("utf-8")),
        ("homeDirectory", f"/home/{login_name}".encode("utf-8")),
        ("loginShell", b"/bin/bash"),
        ("userPassword", slappassword.encode("utf-8")),
        ("sn", last_name.encode("utf-8")),
        ("givenName", given_name.encode("utf-8"))
    ]

    myldap = ldap_connect()
    myldap.add_s(dn, modlist)
    myldap.unbind()
    print('created user '+dn)

    # create homedir of new user
    try:
        check_call(["su", "-l", login_name, "-c", "/bin/ls" ])
    except:
        print('Warning: Could not su as user.')

    print('User created on perfact-login')

def file_sshkey_create(args):
    '''
        user=
        password=
        comment=

        Create a ssh-keypair if it does not exists.
        Never overwrite an existing key.
    '''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py file-sshkey-create',
        description=('Create ssh keypair for user'),
        add_help=True,
    )
    parser.add_argument(
        '--user', type=str,
        help='file user name',
        required=True,
    )
    parser.add_argument(
        '--password', type=str,
        help='ssh passphrase',
        required=True,
    )
    parser.add_argument(
        '--comment', type=str,
        help='ssh comment',
        required=True,
    )
    myargs = parser.parse_args(args)
    login_name = myargs.user
    ssh_pass = myargs.password
    ssh_comment = myargs.comment

    # check if keys exits, than exit
    ssh_keyfile = Path('/home/'+login_name+'/.ssh/id_rsa')
    if ssh_keyfile.is_file():
        # ssh key exits - warn and quit
        print('error: ssh key already exits (no overwrite)')
        exit(1)

    # create a new ssh-keypair with passphare
    cmdcreatesshkey = ''

    try:
        check_call(
            f'su -l {login_name} -c "/usr/bin/ssh-keygen -t rsa -b 4096 -C {ssh_comment} -N {ssh_pass} -f /home/{login_name}/.ssh/id_rsa"',
            shell=True,
        )
    except:
        print('Error: cannot create ssh-key for user.')

    print('SSH key created on perfact-login')


def file_sshkey_show(args):
    '''
        user=

        Print public sshkkey of user.
    '''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py file-sshkey_show',
        description=('Print public sshkey of user'),
        add_help=True,
    )
    parser.add_argument(
        '--user', type=str,
        help='file user name',
        required=True,
    )
    myargs = parser.parse_args(args)
    login_name = myargs.user

    # check if keys exits, than exit
    ssh_keyfile = '/home/'+login_name+'/.ssh/id_rsa'
    if not Path(ssh_keyfile).is_file():
        # ssh key does not exits - warn and quit
        print('error: no ssh key exits')
        exit(1)

    with open(ssh_keyfile, 'r') as f:
        print(f.read())


def file_del_user(args):
    '''
        user=
        [--remove-home] (*optional)
        This will: see above

        remove from groups before deleting user
        ldapsearch base="ou=groups,ou=file,dc=perfact,dc=de"
        and  loop-remove from groups '''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py file-del-user',
        description=('Delete file user from ldap master'),
        add_help=True,
    )
    parser.add_argument(
        '--user', type=str,
        help='file user name',
        required=True,
    )
    parser.add_argument("--remove-home", action="store_true")
    myargs = parser.parse_args(args)

    # first remove user from all groups:
    user_groups = get_groups_of_user(myargs.user)

    if not user_groups:
        print("User not in any group!")
    else:
        for group in user_groups:
            file_del_user_from_group(["--user", myargs.user,
                                      "--group", group])

    # second: remove user from ldap
    # the dn is unknown yet. the uid is the username.
    dn = get_dn_from_uid(myargs.user)

    myldap = ldap_connect()
    myldap.delete_s(dn)
    myldap.unbind()
    print('Removed file user from ldap: '+dn)

    if myargs.remove_home:
        # verify that username is given !
        if (myargs.user == "" or "/" in myargs.user):
            print(
                'Warning: Username is empty or conatins "/".' +
                'Will not delete homedir.')
            exit(1)
        # TODO: activate after testing
        print('rm /home/myargs.user -rf')


def file_add_user_to_group(args):
    '''
        user=
        group=

        This will:
            ldapmodify add memberUid: USER    to
            dn: cn=GROUP,ou=groups,ou=file,dc=perfact,dc=de
            objectClass: posixGroup
            cn: GROUP
            memberUid: existinguser1
            memberUid: existinguser2 ... '''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py file-del-user',
        description=('Add file user to group'),
        add_help=True,
    )
    parser.add_argument(
        '--user', type=str,
        help='file user name',
        required=True,
    )
    parser.add_argument(
        '--group', type=str,
        help='file user group',
        required=True,
    )
    myargs = parser.parse_args(args)

    login_name = myargs.user
    if not file_user_exist(myargs.user):
        print('user does not exists: '+myargs.user)
        exit(1)

    dn = f"cn={myargs.group},ou=groups,ou=file,dc=perfact,dc=de"
    modlist = [
        (ldap.MOD_ADD, "memberUid", login_name.encode("utf-8"))
    ]

    myldap = ldap_connect()
    myldap.modify_s(dn, modlist)
    myldap.unbind()
    print('Added user ' + myargs.user + 'to group ' + myargs.group)


def file_del_user_from_group(args):
    '''
        user=
        group=
    '''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py file-del-user',
        description=('Add file user to group'),
        add_help=True,
    )
    parser.add_argument(
        '--user', type=str,
        help='file user name',
        required=True,
    )
    parser.add_argument(
        '--group', type=str,
        help='file user group',
        required=True,
    )
    myargs = parser.parse_args(args)

    login_name = myargs.user

    dn = f"cn={myargs.group},ou=groups,ou=file,dc=perfact,dc=de"
    modlist = [
        (ldap.MOD_DELETE, "memberUid", login_name.encode("utf-8"))
    ]

    myldap = ldap_connect()
    myldap.modify_s(dn, modlist)
    myldap.unbind()
    print(f"Removed user {myargs.user} from group {myargs.group}")


def file_show_groups_of_user(args):
    '''
        --user

        This will:
            list all goups of a user '''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py file_show_groups_of_user',
        description=('Show groups of user'),
        add_help=True,
    )
    parser.add_argument(
        '--user', type=str,
        help='username ',
        required=True,
    )
    myargs = parser.parse_args(args)

    if not file_user_exist(myargs.user):
        print('user does not exists: '+myargs.user)
        exit(1)

    groups_list = get_groups_of_user(myargs.user)

    print("groups of user:")
    for group in groups_list:
        print(group)


def file_show_groups(args):
    '''
        None

        This will:
            ldapsearch  ou=groups,ou=file,dc=perfact,dc=de
    '''
    groups_list = useradmin_ldap_search(
        'ou=file,dc=perfact,dc=de',
        "objectClass=posixGroup",
        ["cn"])

    print("List of available groups:")
    for group in groups_list:
        print(group[0])

    print('All groups listed')


def file_add_group(args):
    '''
        group =

        This will: 
            created group: cn=testgroup,ou=groups,ou=file,dc=perfact,dc=de
    '''

    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py file-add-group',
        description=('Add a group'),
        add_help=True,
    )

    parser.add_argument(
        '--group', type=str,
        help='file user group',
        required=True,
    )

    myargs = parser.parse_args(args)
    next_gidnumber = useradmin_nextgui("file")
    group_name = myargs.group

    # example data
    # dn: cn=perfactusers,ou=groups,ou=file,dc=perfact,dc=de
    # objectClass: posixGroup
    # gidNumber: 10000
    # cn: perfactusers
    # structuralObjectClass: posixGroup

    dn = "cn=" + myargs.group + ",ou=groups,ou=file,dc=perfact,dc=de"
    modlist = [
        ("objectClass", [b"posixGroup"]),
        ("cn", group_name.encode("utf-8")),
        ("gidNumber", next_gidnumber.encode("utf-8")),
    ]

    myldap = ldap_connect()
    myldap.add_s(dn, modlist)
    myldap.unbind()
    print('created group: '+dn)


def file_del_group(args):
    '''
        This will:
            deleted group: cn=testgroup,ou=groups,ou=file,dc=perfact,dc=de
    '''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py file-del-group', 
        description=('Delete group'),
        add_help=True,
    )

    parser.add_argument(
        '--group', type=str,
        help='file user group',
        required=True,
    )

    myargs = parser.parse_args(args)

    dn = "cn=" + myargs.group + ",ou=groups,ou=file,dc=perfact,dc=de"

    myldap = ldap_connect()
    myldap.delete_s(dn)
    myldap.unbind()
    print('Deleted group: '+dn)


def file_show_group(args):
    '''
        --group

        This will:
            ldapsearch  cn=GROUP,ou=groups,ou=file,dc=perfact,dc=de
         and return all memberUid
    '''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py file-del-user',
        description=('Show members of group'),
        add_help=True,
    )
    parser.add_argument(
        '--group', type=str,
        help='file user group',
        required=True,
    )
    myargs = parser.parse_args(args)
    group_name = myargs.group

    group_list = useradmin_ldap_search(
        'ou=file,dc=perfact,dc=de',
        f"cn={group_name}",
        ["memberUid"])

    print(f"Members of group {group_name}:")
    for group in group_list:
        for member in group[1]["memberUid"]:
            print(member.decode('utf-8'))

    print('All members listed')


def file_set_passwd(args):
    '''
        user=
        password=

    This will: set user password in ldap:
    dn: uid=LOGINNAME,ou=users,ou=file,dc=perfact,dc=de
    userPassword:: CRYPTEDPASS'''
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py file-set-passwd',
        description=('Set password of fileuser in ldap.'),
        add_help=True,
    )
    parser.add_argument(
        '--user', type=str,
        help='file user name',
        required=True,
    )
    parser.add_argument(
        '--password', type=str,
        help='file user password',
        required=True,
    )
    myargs = parser.parse_args(args)

    login_name = myargs.user
    slappassword = useradmin_slappasswd(myargs.password)

    dn =  get_dn_from_uid(login_name)
    modlist = [
        (ldap.MOD_REPLACE, "userPassword", slappassword.encode("utf-8")),
    ]
    myldap = ldap_connect()
    myldap.modify_s(dn, modlist)
    myldap.unbind()
    print('Set user password: ' + dn)

# ### commands for perfact-file ###
def file_create_home(args):
    # let pam create the homedir by su to user
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py file-create-home',
        description=('Create homedir for user'),
        add_help=True,
    )
    parser.add_argument(
        '--user', type=str,
        help="create a homedir for user",
        required=True
    )
    myargs = parser.parse_args(args)

    try:
        check_call(
            [
                'su',
                '-l',
                myargs.user,
                '-c',
                '/usr/bin/date',
            ]
        )
    except CalledProcessError as error:
        print(
            "Error when running command 'su -l ",
            myargs.user,
            " -c /usr/bin/date",
            "'",
        )
        raise error


# ### commands for pf-phonehome ###

def phonehome_deluser(args):
    # remove user including homedir
    parser = argparse.ArgumentParser(
        prog='perfact_useradmin.py phonehome-deluser',
        description=('Remove user account including homedir'),
        add_help=True,
    )
    parser.add_argument(
        '--username', type=str,
        help="username to delete from system",
        required=True
    )
    myargs = parser.parse_args(args)

    try:
        check_call(
            [
                'deluser',
                '--remove-home',
                myargs.username,
            ]
        )
    except CalledProcessError as error:
        print(
            "Error when running command 'deluser --remove-home ",
            myargs.username,
            "'",
        )
        raise error


# ### commands for perfact-ema-prod ###


def check_single_secrets(args):
    # check-single-secrets will do:
    #   return true, if there is a secret with only one user
    #   than we stop remove any psafe from perfact-login*
    # HOWTO do this:
    # start the script /opt/perfact/custom/show_single_cryptinfo.sh
    # whis will do:
    # echo "select cryptinfo_wiki_id, string_agg(cryptinfo_user, ', ')
    #    from cryptinfo group by 1 having count(*) < 2;" |
    #    su - postgres -c "psql perfactema"

    try:
        result = run(
            [
                '/opt/perfact/custom/show_single_cryptinfo.sh',
            ],
            capture_output=True
        ).stdout
    except CalledProcessError as error:
        print(
            'Error when running command:',
        )
        raise error

    '''
        b' cryptinfo_wiki_id | string_agg \n
        -------------------+------------\n
        (0 rows)\n
        \n
        '
    '''
    print(result.decode('utf-8'))
