#!/usr/bin/python3
import sys, argparse, struct, os.path, datetime, uuid, re
from sys import exit
from time import sleep
from configparser import ConfigParser
import pam, netifaces, ldap
from getpass import getpass, getuser
from subprocess import Popen, PIPE
from base64 import b64decode, b64encode
from ldap.modlist import addModlist as addlist
from ldap.modlist import modifyModlist as modlist
import pwd, grp
from samba.param import LoadParm
from dns import resolver, reversename

nsswitch = '/etc/nsswitch.conf'
krb5_conf = '/etc/krb5.conf'
winbindd = '/usr/sbin/winbindd'
smbd = '/usr/sbin/smbd'
pam_auth_conf = '/etc/pam.d/common-auth'
pam_password_conf = '/etc/pam.d/common-password'
pam_session_conf = '/etc/pam.d/common-session'
pam_module = '/usr/lib64/security/pam_winbind.so'
winbind_conf = '/etc/security/pam_winbind.conf'
net = '/usr/bin/net'
samba_tool = '/usr/bin/samba-tool'
samba = '/usr/sbin/samba'
service = '/usr/sbin/service'
systemctl = '/usr/bin/systemctl'
admin = None
password = None
debug_level = '0'

class SMBConfigParser(ConfigParser):
    def __init__(self, defaults=None, dict_type=None, allow_no_value=None):
        ConfigParser.__init__(self, defaults, dict_type, allow_no_value, interpolation=None)

    def _read(self, fp, fpname):
        cursect = None
        sectname = None
        optname = None
        e = None
        _SECT_TMPL = r"""\[(?P<header>[^]]+)\]"""
        _OPT_TMPL = r"""\s*(?P<option>.*?)\s*(?P<vi>=)\s*(?P<value>.*)$"""
        for lineno, line in enumerate(fp, start=1):
            if not line.strip() or line.strip()[0] == '#':
                continue
            if '#' in line:
                line = line.split('#')[0]
            mo = re.match(_SECT_TMPL, line)
            if mo:
                sectname = mo.group('header')
                if sectname in self._sections:
                    raise DuplicateSectionError(sectname, fpname, lineno)
                else:
                    cursect = self._dict()
                    cursect['__name__'] = sectname
                    self._sections[sectname] = cursect
                optname = None
            elif cursect is None:
                raise MissingSectionHeaderError(fpname, lineno, line)
            else:
                mo = re.match(_OPT_TMPL, line)
                if mo:
                    optname, vi, optval = mo.group('option', 'vi', 'value')
                    cursect[optname] = [optval.strip()]
                else:
                    e = self._handle_error(e, fpname, lineno, line)
        if e:
            raise e
        all_sections = [self._defaults]
        all_sections.extend(self._sections.values())
        for options in all_sections:
            for name, val in options.items():
                if isinstance(val, list):
                    options[name] = '\n'.join(val)

def kdc_installed():
    return os.path.exists(samba)

def stop_samba(service=None):
    global debug_level
    if not service:
        service = 'samba|smbd|nmbd|winbindd'
    lines = Popen(['ps -eo \'pid,ppid,cmd\' | egrep "%s" | grep -v grep' % service], shell=True, stdout=PIPE).communicate()[0].strip()
    if lines:
        procs = []
        all_procs = []
        for line in lines.decode('utf-8').split('\n'):
            data = line.strip().split()
            all_procs.append(int(data[1]))
            if data and len(data) > 1 and data[1] == '1':
                procs.append(data[0])
        cmd = ['kill']
        if len(procs) == 0:
            cmd.extend('%d' % min(all_procs))
        else:
            cmd.extend(procs)
        if debug_level != '0':
            print(' '.join(cmd))
        Popen(cmd).wait()

def clean_samba_db():
    samba_dirs = [line.split(':')[-1].strip() for line in Popen(['%s -b | egrep "LOCKDIR|STATEDIR|CACHEDIR|PRIVATE_DIR"' % smbd], shell=True, stdout=PIPE).communicate()[0].decode('utf-8').strip().split('\n')]
    for topdir in samba_dirs:
        for root, dirs, files in os.walk(topdir):
            for fname in files:
                if fname.endswith('.tdb') or fname.endswith('.ldb'):
                    os.remove(os.path.join(root, fname))

smb_conf = None
def get_smb_conf():
    global smb_conf
    if not smb_conf:
        smb_conf = Popen(['grep', 'CONFIGFILE'], stdin=Popen([smbd, '-b'], stdout=PIPE).stdout, stdout=PIPE).communicate()[0].decode('utf-8').strip().split(':')[-1].strip()
    return smb_conf

samba_version = None
def get_samba_version():
    global samba_version
    if not samba_version:
        data = Popen([smbd, '-V'], stdout=PIPE).communicate()[0].strip()
        samba_version = re.findall('\d+\.\d+\.\d', data.decode('utf-8').split()[1])[0]
    return samba_version

def get_default_realm_int():
    lp = LoadParm()
    lp.load(get_smb_conf())
    return lp.get('realm')

default_realm = None
def get_default_realm():
    global default_realm
    if not default_realm:
        default_realm = get_default_realm_int()
    return default_realm

def get_netbios_name(realm):
    global admin, password
    admin, password = get_creds(admin, password)
    l = ldap_open(realm, admin, password)

    results = l.search_s('CN=Partitions,CN=Configuration,%s' % realm_to_dn(realm), ldap.SCOPE_SUBTREE, '(netbiosname=*)', [])
    return results[0][1]['cn'][-1] if results and len(results) > 0 and len(results[0]) > 1 and 'cn' in results[0][1] and len(results[0][1]['cn']) > 0 else ''

def user_list(admin, password, debug_level):
    global net
    cmd = [net, '--configfile=%s' % get_smb_conf(), 'rpc', 'user', '-U%s%%%s' % (admin, password), '-S', get_default_realm()]
    if debug_level != '0':
        print(' '.join(cmd))
    return Popen(cmd, stdout=PIPE).communicate()[0].strip().split('\n')

def group_list(admin, password, debug_level):
    global net
    cmd = [net, '--configfile=%s' % get_smb_conf(), 'rpc', 'group', '-U%s%%%s' % (admin, password), '-S', get_default_realm()]
    if debug_level != '0':
        print(' '.join(cmd))
    return Popen(cmd, stdout=PIPE).communicate()[0].strip().split('\n')

def getpwnam(user):
    try:
        return ':'.join([str(i) for i in pwd.getpwnam(user)])
    except KeyError:
        return ''

def getgrnam(group):
    try:
        g = grp.getgrnam(group)
        return ':'.join([str(i) for i in g[-1]])+':'+','.join(g.gr_mem)
    except KeyError:
        return ''

def getpwent(admin, password, debug_level):
    ulist = []
    for d in pwd.getpwall():
        ulist.append(':'.join([str(i) for i in d]))
    for u in user_list(admin, password, debug_level):
        ulist.append(getpwnam(u))
    return ulist

def getgrent(admin, password, debug_level):
    glist = []
    for d in grp.getgrall():
        glist.append(':'.join([str(i) for i in d[:-1]])+':'+','.join(d.gr_mem))
    for g in group_list(admin, password, debug_level):
        glist.append(getgrnam(g))
    return glist

def get_creds(admin, password):
    if not admin:
        admin = raw_input("Username: ")
    if not password:
        password = getpass("%s's Password: " % admin)
    return (admin, password)

def pam_conv(auth, query_list):
    resp = []
    for (query, type) in query_list:
        # Never echo
        if type == PAM.PAM_PROMPT_ECHO_ON or PAM.PAM_PROMPT_ECHO_OFF:
            resp.append((getpass(query), 0))
        else:
            print(query)
            resp.append(('', 0))
    return resp

def ldap_posix_user(user, container):
    global admin, password
    l = ldap_open(get_default_realm(), admin, password)
    results = l.search_s(container, ldap.SCOPE_SUBTREE, '(cn=%s)' % args.object, ['sAMAccountName', 'uidNumber', 'gidNumber', 'gecos', 'homeDirectory', 'loginShell'])
    if len(results) == 1:
        result = results[0][1]
        data = '%s:x' % result['sAMAccountName'][-1]
        for key in ['uidNumber', 'gidNumber', 'gecos', 'homeDirectory', 'loginShell']:
            arg = result[key][-1] if key in result.keys() else ''
            data += ':%s' % arg
        return data

def ldap_posix_pwent(container):
    global admin, password
    ulist = []
    for u in user_list(admin, password, debug_level):
        ulist.append(ldap_posix_user(u, container))
    return ulist

def nss(args, unknownargs):
    global admin, password, debug_level
    if len(unknownargs) > 0:
        args.help_func()
        exit(1)
    realm = get_default_realm()
    if args.s2 == 'getpwnam':
        if args.direct:
            pw = ldap_posix_user(args.object, user_container())
        else:
            pw = getpwnam(args.object)
            if not pw and not args.object.lower().endswith(realm.lower()) and not args.object.lower().startswith(realm.lower()):
                pw = getpwnam('%s@%s' % (args.object, realm))
        if pw:
            print(pw)
    elif args.s2 == 'getpwuid':
        pw = getpwnam(args.object)
        if pw:
            print(pw)
    elif args.s2 == 'getgrnam' or args.s2 == 'getgrid':
        gr = getgrnam(args.object)
        if not gr and not args.object.lower().endswith(realm.lower()) and not args.object.lower().startswith(realm.lower()):
            gr = getgrnam('%s@%s' % (args.object, realm))
        if gr:
            print(gr)
    elif args.s2 == 'getpwent' or args.s2 == 'users':
        admin, password = get_creds(admin, password)
        if args.direct:
            ulist = ldap_posix_pwent(user_container())
        else:
            ulist = getpwent(admin, password, debug_level)
        for pw in ulist:
            if pw and pw.strip():
                print(pw)
    elif args.s2 == 'getgrent' or args.s2 == 'groups':
        admin, password = get_creds(admin, password)
        glist = getgrent(admin, password, debug_level)
        for gr in glist:
            if gr.strip():
                print(gr)

def unix_enable_user(name, passwd, container=None):
    global admin, password, debug_level
    admin, password = get_creds(admin, password)
    l = ldap_open(get_default_realm(), admin, password)
    dn = getdn(args.object)
    data = passwd.split(':')

    ldif_old = {'uidNumber': [''], 'gidNumber': [''], 'gecos': [''], 'homeDirectory': [''], 'loginShell': ['']}
    ldif = {'uidNumber': [data[2]], 'gidNumber': [data[3]], 'gecos': [data[4]], 'homeDirectory': [data[5]], 'loginShell': [data[6]]}

    try:
        l.modify_s(dn, modlist(ldif_old, ldif))
    except ldap.NO_SUCH_ATTRIBUTE:
        l.modify_s(dn, modlist({}, ldif))

def create(args, unknownargs):
    global admin, password, debug_level, net
    if len(unknownargs) > 0:
        args.help_func()
        exit(1)
    admin, password = get_creds(admin, password)
    if args.s2 == 'user':
        if not args.e:
            new_user_pass = getpass("New password: ")
            cmd = [net, '--configfile=%s' % get_smb_conf(), 'rpc', 'user', 'add', args.object, new_user_pass, '-U%s%%%s' % (admin, password), '-S', get_default_realm(), '-d', debug_level]
            if debug_level != '0':
                print(' '.join(cmd))
            ret = Popen(cmd).wait()
            if ret:
                return ret
        if args.i:
            return unix_enable_user(args.object, args.i, args.c)
    elif args.s2 == 'group':
        cmd = [net, '--configfile=%s' % get_smb_conf(), 'rpc', 'group', 'add', args.object, '-U%s%%%s' % (admin, password), '-S', get_default_realm(), '-d', debug_level]
        if debug_level != '0':
            print(' '.join(cmd))
        return Popen(cmd).wait()

def delete(args, unknownargs):
    global admin, password, debug_level, net
    if len(unknownargs) > 0:
        args.help_func()
        exit(1)
    admin, password = get_creds(admin, password)
    if args.s2 == 'user':
        cmd = [net, '--configfile=%s' % get_smb_conf(), 'rpc', 'user', 'delete', args.object, '-U%s%%%s' % (admin, password), '-S', get_default_realm(), '-d', debug_level]
        if debug_level != '0':
            print(' '.join(cmd))
        return Popen(cmd).wait()
    elif args.s2 == 'group':
        cmd = [net, '--configfile=%s' % get_smb_conf(), 'rpc', 'group', 'delete', args.object, '-U%s%%%s' % (admin, password), '-S', get_default_realm(), '-d', debug_level]
        if debug_level != '0':
            print(' '.join(cmd))
        return Popen(cmd).wait()

def user(args, unknownargs):
    global admin, password, debug_level
    if len(unknownargs) > 0:
        args.help_func()
        exit(1)
    if args.s2 == 'checklogin' and args.user:
        p = PAM.pam()
        p.start('passwd')
        p.set_item(PAM.PAM_USER, args.user)
        p.set_item(PAM.PAM_CONV, pam_conv)
        try:
            p.authenticate()
        except:
            print('Authentication failed')
        else:
            print('Authentication succeeded')
        try:
            p.acct_mgmt()
        except:
            print('User is not allowed')
        else:
            print('User is allowed')
    elif args.s2 == 'checkaccess' and args.user:
        p = PAM.pam()
        p.start('passwd')
        p.set_item(PAM.PAM_USER, args.user)
        try:
            p.acct_mgmt()
        except:
            print('User is not allowed')
        else:
            print('User is allowed')

def passwd(args, unknownargs):
    global admin, password, debug_level, net
    if len(unknownargs) > 0:
        args.help_func()
        exit(1)
    admin, password = get_creds(admin, password)
    if args.object:
        user = args.object
    else:
        user = getuser()
    cmd = [net, '--configfile=%s' % get_smb_conf(), 'rpc', 'password', user, '-U%s%%%s' % (admin, password), '-S', get_default_realm(), '-d', debug_level]
    if debug_level != '0':
        print(' '.join(cmd))
    return Popen(cmd).wait()

# http://stackoverflow.com/questions/33188413/python-code-to-convert-from-objectsid-to-sid-representation
def convert_objsid_to_sidstr(binary):
    version = struct.unpack('B', binary[0])[0]
    # I do not know how to treat version != 1 (it does not exist yet)
    assert version == 1, version
    length = struct.unpack('B', binary[1])[0]
    authority = struct.unpack('>Q', '\x00\x00' + binary[2:8])[0]
    string = 'S-%d-%d' % (version, authority)
    binary = binary[8:]
    assert len(binary) == 4 * length
    for i in xrange(length):
        value = struct.unpack('<L', binary[4*i:4*(i+1)])[0]
        string += '-%d' % (value)
    return string

def realm_to_dn(realm):
    return ','.join(['dc=%s' % part for part in realm.split('.')])

def kinit_admin(user, password):
    global debug_level, krb5_conf
    realm = get_default_realm()
    cmd = ['kinit']
    if debug_level != '0':
        cmd.append('-V')
    if not realm in user:
        user = '%s@%s' % (user, realm.upper())
    cmd.append(user)
    if debug_level != '0':
        print(' '.join(cmd))
    ret = 0
    p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
    error = None
    try:
        p.stdin.write(b'%s\n' % password.encode('utf-8'))
        ret = p.wait()
    except Exception as e:
        ret = 1
        error = str(e)
    if ret != 0:
        data = p.communicate()
        m = re.findall('Credential cache directory (.*) does not exist', data[1].decode('utf-8'))
        if len(m) > 0:
            os.makedirs(m[-1])
            p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
            p.stdin.write(b'%s\n' % password.decode('utf-8'))
            ret = p.wait()
        else:
            print(error)
    return ret

ldap_open_connections = {}
def ldap_open(realm, user, password):
    global ldap_open_connections
    key = '%s:%s' % (realm, user)
    if key not in ldap_open_connections.keys():
        if kinit_admin(user, password) != 0:
            print('Authentication failed')
            exit()
        l = ldap.initialize('ldap://%s' % realm)
        try:
            auth_tokens = ldap.sasl.gssapi('')
            l.sasl_interactive_bind_s('', auth_tokens)
        except ldap.LDAPError as e:
            print(e.message['info'])
            if type(e.message) == dict and e.message.has_key('desc'):
                print(e.message['desc'])
            else:
                print(e)
            exit()
        ldap_open_connections[key] = l
    return ldap_open_connections[key]

wkguiduc = 'A9D1CA15768811D1ADED00C04FD8D5CD'
uc = None
def user_container():
    global admin, password, uc, wkguiduc
    if not uc:
        admin, password = get_creds(admin, password)
        l = ldap_open(get_default_realm(), admin, password)
        results = l.search_s('<WKGUID=%s,%s>' % (wkguiduc, realm_to_dn(get_default_realm())), ldap.SCOPE_SUBTREE, '(objectClass=container)', ['distinguishedName'])
        uc = results[0][1]['distinguishedName'][-1]
    return uc

def print_ldap_object(obj, sidstr):
    for key in obj.keys():
        if key in ['logonHours', 'objectGUID', 'objectSid']:
            if key == 'objectSid' and sidstr:
                obj[key] = [convert_objsid_to_sidstr(o) for o in obj[key]]
            else:
                obj[key] = [b64encode(o) for o in obj[key]]
            for ob in obj[key]:
                print('%s:: %s' % (key, ob))
        else:
            for ob in obj[key]:
                print('%s: %s' % (key, ob))

def attrs(args, unknownargs):
    global admin, password, debug_level
    if len(unknownargs) > 0:
        args.help_func()
        exit(1)
    if args.object:
        admin, password = get_creds(admin, password)
        l = ldap_open(get_default_realm(), admin, password)
        container = args.c
        if not container:
            container = user_container()
        if debug_level != '0':
            print('ldapsearch', '-LLL', '-x', '-h', get_default_realm(), '-D', '%s@%s' % (admin, get_default_realm()), '-W', '-b', container, '"(cn=%s)"' % args.object, ' '.join(args.attributes))
        results = l.search_s(container, ldap.SCOPE_SUBTREE, '(cn=%s)' % args.object, args.attributes)
        for result in results:
            print_ldap_object(result[1], args.b)
            print()
    else:
        args.help_func()

def getdn(cn, container=None):
    global admin, password
    admin, password = get_creds(admin, password)
    l = ldap_open(get_default_realm(), admin, password)
    if not container:
        container = user_container()
    results = l.search_s(container, ldap.SCOPE_SUBTREE, '(cn=%s)' % cn, ['distinguishedName'])
    return results[0][1]['distinguishedName'][-1]

def setattrs(args, unknownargs):
    global admin, password, debug_level
    if len(unknownargs) > 0:
        args.help_func()
        exit(1)
    admin, password = get_creds(admin, password)
    l = ldap_open(get_default_realm(), admin, password)
    dn = getdn(args.object)
    ldif_old = {args.attribute: ['']}
    ldif = {args.attribute: [args.value]}
    try:
        l.modify_s(dn, modlist(ldif_old, ldif))
    except ldap.NO_SUCH_ATTRIBUTE:
        l.modify_s(dn, modlist({}, ldif))

def timesync(args, unknownargs):
    if len(unknownargs) > 0:
        args.help_func()
        exit(1)
    if args.s:
        Popen(['service', 'ntpd', 'stop']).wait()
        Popen(['/usr/sbin/ntpdate', args.s]).wait()
        Popen(['service', 'ntpd', 'start']).wait()

def config_ntp(servers):
    ntp_conf = '/etc/ntp.conf'
    if not os.path.exists(ntp_conf):
        sys.stderr.write('ntp not found. Package install required.\nzypper in ntp\n')
        exit(1)

    # stop the ntp service
    Popen(['service', 'ntpd', 'stop']).wait()

    config = ''
    for line in open(ntp_conf):
        if line.strip() and line.strip().split()[0] != 'server': # throw out old server list
            config += line
    for server in servers:
        config += '\nserver %s\n' % server
    of = open(ntp_conf, 'w')
    of.write(config)

    # tell ntp to update the time
    Popen(['/usr/sbin/ntpdate', servers[0]]).wait()

    # restart ntp
    Popen(['service', 'ntpd', 'start']).wait()

def config_smb_conf(domain, autogen=False, server=False):
    smb_conf = get_smb_conf()
    conf = SMBConfigParser()
    if os.path.exists(smb_conf):
        conf.read(smb_conf)
    if 'global' not in conf.sections():
        conf.add_section('global')
    if not server:
        netbios = get_netbios_name(domain)
        conf.set('global', 'security', 'ads')
        conf.set('global', 'workgroup', domain.split('.')[0].upper())
        conf.set('global', 'realm', domain.upper())
        conf.set('global', 'log file', '/var/log/samba/%m.log')
        conf.set('global', 'log level', '1')
        conf.set('global', 'passdb backend', 'tdbsam')
        conf.set('global', 'map to guest', 'Bad User')
        conf.set('global', 'logon path', '\\\\%L\\profiles\\.msprofile')
        conf.set('global', 'logon home', '\\\\%L\\%U\\.9xprofile')
        conf.set('global', 'logon drive', 'P:')
        conf.set('global', 'usershare allow guests', 'yes')
        conf.set('global', 'winbind offline logon', 'yes')
        if autogen:
            conf.set('global', 'template shell', '/bin/bash')
            conf.set('global', 'template homedir', '/home/%D/%U')
            conf.set('global', 'idmap config * : backend', 'tdb')
            conf.set('global', 'idmap config * : range', '2000-3999')
            conf.set('global', 'idmap config %s : backend' % netbios, 'rid')
            conf.set('global', 'idmap config %s : range' % netbios, '4000-99999')
            if float(get_samba_version()[:3]) >= 4.6:
                conf.set('global', 'idmap config *:unix_nss_info', 'no')
            else:
                conf.set('global', 'winbind nss info', 'template')
        else:
            conf.set('global', 'idmap config *:backend', 'autorid')
            conf.set('global', 'idmap config *:range', '2000-3999')
            conf.set('global', 'idmap config %s:backend' % netbios, 'ad')
            conf.set('global', 'idmap config %s:schema_mode' % netbios, 'rfc2307')
            conf.set('global', 'idmap config %s:range' % netbios, '4000-99999')
            if float(get_samba_version()[:3]) >= 4.6:
                conf.set('global', 'idmap config %s:unix_nss_info' % netbios, 'yes')
            else:
                conf.set('global', 'winbind nss info', 'rfc2307')
    else:
        conf.set('global', 'winbind nss info', 'template')
        conf.set('global', 'template shell', '/bin/bash')
        conf.set('global', 'template homedir', '/home/%D/%U')
    of = open(smb_conf, 'w')
    conf.write(of)
    of.close()

def config_winbind_conf():
    global winbind_conf
    conf = SMBConfigParser()
    if os.path.exists(winbind_conf):
        conf.read(winbind_conf)
    if 'global' not in conf.sections():
        conf.add_section('global')
    conf.set('global', 'cached_login', 'yes')
    conf.set('global', 'krb5_auth', 'yes')
    conf.set('global', 'krb5_ccache_type', 'FILE')
    conf.set('global', 'warn_pwd_expire', '14')
    conf.set('global', 'mkhomedir', 'yes')
    of = open(winbind_conf, 'w')
    conf.write(of)
    of.close()

def follow_symlinks(files):
    for name in files:
        if os.path.exists(name) and os.path.islink(name):
            print('%s links to %s' % (name, os.path.realpath(name)))

def configure_nsswitch_conf():
    global nsswitch
    try:
        conf = ''
        for line in open(nsswitch):
            fore = line.strip().split(':')[0]
            if (fore == 'passwd' or fore == 'group') and 'winbind' not in line:
                conf += '%s winbind\n' % line.replace('[NOTFOUND=return]', '').rstrip()
            else:
                conf += line
        of = open(nsswitch, 'w')
        of.write(conf)
    except IOError:
        sys.stderr.write('Configure nsswitch failed, filename \'%s\' not found\n' % nsswitch)

def configure_nss():
    if not os.path.exists('/usr/lib64/libnss_winbind.so'):
        if os.path.exists('/usr/lib64/libnss_winbind.so.2'):
            os.symlink('/usr/lib64/libnss_winbind.so.2', '/usr/lib64/libnss_winbind.so')
            follow_symlinks(['/usr/lib64/libnss_winbind.so'])
            configure_nsswitch_conf()
        else:
            sys.stderr.write('Cannot find /usr/lib64/libnss_winbind.so, check that you\'ve installed the library\n')
    else:
        follow_symlinks(['/usr/lib64/libnss_winbind.so'])
        configure_nsswitch_conf()

def unconfigure_nss():
    global nsswitch
    try:
        conf = ''
        for line in open(nsswitch):
            fore = line.strip().split(':')[0]
            if (fore == 'passwd' or fore == 'group') and 'winbind' in line:
                conf += line.replace('winbind', '')
            else:
                conf += line
        of = open(nsswitch, 'w')
        of.write(conf)
    except IOError:
        sys.stderr.write('Unconfigure nsswitch failed, filename \'%s\' not found\n' % nsswitch)

def configure_pam_auth(pam_conf):
    global pam_module
    conf = ''
    for line in open(pam_conf):
        if 'pam_unix.so' in line and line.split()[0] == 'auth':
            conf += 'auth\tsufficient\tpam_unix.so try_first_pass\n'
            conf += 'auth\trequired\t%s use_first_pass\n' % pam_module
        elif not pam_module.split('/')[-1] in line:
            conf += line
    of = open(pam_conf, 'w')
    of.write(conf)

def configure_pam_password(pam_conf):
    global pam_module
    conf = ''
    first_line = True
    for line in open(pam_conf):
        if line.strip()[0] != '#' and first_line:
            conf += 'password\tsufficient\t%s\n' % pam_module
            first_line = False
        elif not pam_module.split('/')[-1] in line:
            conf += line
    of = open(pam_conf, 'w')
    of.write(conf)

def configure_pam_session(pam_conf):
    conf = ''
    for line in open(pam_conf):
        if 'session' in line and 'pam_unix.so' in line:
            conf += 'session\toptional\tpam_mkhomedir.so\n'
        elif not 'pam_mkhomedir.so' in line:
            conf += line
    of = open(pam_conf, 'w')
    of.write(conf)

def configure_pam():
    global pam_auth_conf, pam_password_conf, pam_module, pam_session_conf

    if not os.path.exists(pam_module):
        if os.path.exists('/lib64/security/pam_winbind.so'):
            if not os.path.exists('/usr/lib64/security'):
                os.mkdir('/usr/lib64/security')
            os.symlink('/lib64/security/pam_winbind.so', pam_module)
            configure_pam_auth(pam_auth_conf)
            configure_pam_password(pam_password_conf)
            configure_pam_session(pam_session_conf)
            follow_symlinks([pam_module])
        else:
            sys.stderr.write('Cannot find /usr/lib64/security/pam_winbind.so, check that you\'ve installed the library\n')
    else:
        configure_pam_auth(pam_auth_conf)
        configure_pam_password(pam_password_conf)
        configure_pam_session(pam_session_conf)
        follow_symlinks([pam_module])

def unconfigure_pam_password():
    global pam_module, pam_password_conf
    conf = ''
    for line in open(pam_password_conf):
        if not pam_module.split('/')[-1] in line:
            conf += line
    of = open(pam_password_conf, 'w')
    of.write(conf)

def unconfigure_pam_auth():
    global pam_module, pam_auth_conf
    conf = ''
    first_line = True
    for line in open(pam_auth_conf):
        if 'pam_unix.so' in line and line.split()[0] == 'auth':
            conf += 'auth\trequired\tpam_unix.so try_first_pass\n'
        elif not pam_module.split('/')[-1] in line:
            conf += line
    of = open(pam_auth_conf, 'w')
    of.write(conf)

def unconfigure_pam_session():
    global pam_session_conf
    conf = ''
    for line in open(pam_session_conf):
        if not 'pam_mkhomedir.so' in line:
            conf += line
    of = open(pam_session_conf, 'w')
    of.write(conf)

def unconfigure_pam():
    unconfigure_pam_auth()
    unconfigure_pam_password()
    unconfigure_pam_session()

def configure(args, unknownargs):
    if len(unknownargs) > 0:
        args.help_func()
        exit(1)
    if args.s2 == 'pam':
        configure_pam()
    elif args.s2 == 'nss':
        configure_nss()

def unconfigure(args, unknownargs):
    if len(unknownargs) > 0:
        args.help_func()
        exit(1)
    if args.s2 == 'pam':
        unconfigure_pam()
    elif args.s2 == 'nss':
        unconfigure_nss()

def ip_addrs():
    ifaces = [netifaces.ifaddresses(interface) for interface in netifaces.interfaces()]
    return [data[2][0]['addr'] for data in ifaces if len(data)>2 and len(data[2])>0 and 'addr' in data[2][0] and data[2][0]['addr'] != '127.0.0.1']

def update_hostname(hostname, domain):
    # Retrieve the current hostname if one was specified
    shortname = None
    if not hostname:
        hostname = Popen(['hostname'], stdout=PIPE).communicate()[0].strip()
    if not domain.lower() in hostname:
        shortname = hostname
        hostname = '%s.%s\n' % (hostname, domain)
    else:
        shortname = hostname.split('.')[0]
    hostname = hostname.strip()
    shortname = shortname.strip()

    # Update the /etc/hostname file
    hf = open('/etc/hostname', 'w')
    hf.write('%s' % hostname)

    # Update the in-memory hostname
    Popen(['hostname', hostname]).wait()

    # Add an entry to /etc/hosts so we can resolve our own name
    hosts = '/etc/hosts'
    ips = ip_addrs()
    conf = ''
    for line in open(hosts):
        if hostname not in line:
            conf += line
    conf = conf.strip()
    conf += '\n'
    # Only the last one is used, may need to be manually configured
    for ip in ips:
        conf += '%s\t%s %s\n' % (ip, hostname, shortname)
    of = open(hosts, 'w')
    of.write(conf)

    return (hostname, shortname)

def remove_hosts_config():
    hostname = Popen(['hostname'], stdout=PIPE).communicate()[0].strip().decode('utf-8')
    hosts = '/etc/hosts'
    conf = ''
    for line in open(hosts):
        if hostname not in line:
            conf += line
    of = open(hosts, 'w')
    of.write(conf)

def krb5_basic_conf(domain):
    global krb5_conf
    kof = open(krb5_conf, 'w')
    kof.write('[libdefaults]\n')
    kof.write('\tdns_lookup_realm = false\n')
    kof.write('\tdns_lookup_kdc = true\n')
    kof.write('\tdefault_realm = %s\n' % domain)

def config_krb5_conf(domain, server):
    global krb5_conf
    kof = open(krb5_conf, 'w')
    kof.write('[libdefaults]\n')
    kof.write('\tdefault_realm = %s\n' % domain)
    kof.write('\tclockskew = 300\n')
    kof.write('\tticket_lifetime = 1d\n')
    kof.write('\tforwardable = true\n')
    kof.write('\tproxiable = true\n')
    kof.write('\tdns_lookup_realm = true\n')
    kof.write('\tdns_lookup_kdc = true\n')
    kof.write('\tudp_preference_limit = 1\n') # disable UDP packets
    kof.write('\n\n[realms]\n')
    kof.write('\t%s = {\n\t\tkdc = %s\n\t\tadmin_server = %s\n\t\tdefault_domain = %s\n\t}\n' % (domain, server, server, domain))

def list_servers(domain):
    return [str(resolver.query(reversename.from_address(r.address),"PTR")[0])[:-1] for r in resolver.query(domain, 'A')]

def start_enable_service(name, binpath):
    global service, systemctl
    if os.path.exists(systemctl):
        Popen([systemctl, 'enable', name], stdout=PIPE, stderr=PIPE).wait()
        Popen([systemctl, 'daemon-reload'], stdout=PIPE, stderr=PIPE).wait()
        if Popen([systemctl, 'restart', name], stdout=PIPE, stderr=PIPE).wait() == 0:
            return
    if os.path.exists(service):
        if Popen([service, name, 'restart'], stdout=PIPE, stderr=PIPE).wait() == 0:
            return
    if os.path.exists(binpath):
        if Popen([binpath, '-d', debug_level, '--configfile=%s' % get_smb_conf(), '-D'], stdout=PIPE, stderr=PIPE).wait() == 0:
            return
    return -1

def stop_disable_service(name):
    global service, systemctl
    if os.path.exists(systemctl):
        Popen([systemctl, 'stop', name], stdout=PIPE, stderr=PIPE).wait()
        if Popen([systemctl, 'disable', name], stdout=PIPE, stderr=PIPE).wait() == 0:
            return
    if os.path.exists(service):
        if Popen([service, name, 'stop'], stdout=PIPE, stderr=PIPE).wait() == 0:
            return
    stop_samba(name)

def stash_config():
    global krb5_conf
    # Stash the old smb.conf
    if os.path.exists(get_smb_conf()):
        smb_conf = get_smb_conf()
        now = datetime.datetime.now()
        new_smb_conf = '%s.%s' % (smb_conf, now.strftime('%b-%d-%Y_%I:%M%p'))
        print('Stashing smb.conf to %s...' % new_smb_conf)
        os.rename(smb_conf, new_smb_conf)
    # Stash any old krb5.conf
    if os.path.exists(krb5_conf):
        now = datetime.datetime.now()
        new_krb5_conf = '%s.%s' % (krb5_conf, now.strftime('%b-%d-%Y_%I:%M%p'))
        print('Stashing krb5.conf to %s...' % new_krb5_conf)
        os.rename(krb5_conf, new_krb5_conf)

def start_samba():
    global samba
    start_enable_service('samba', samba)

    # Make sure winbindd started also
    ret = Popen(['ps ax | egrep "winbindd" | grep -v grep'], shell=True, stdout=PIPE).wait()
    if ret != 0:
        # If winbindd failed to start, try fixing it
        stop_samba()

        # Stop apparmor, since that could block winbindd from starting
        stop_samba('apparmor')

        # Try again
        ret = start_enable_service('samba', samba)

    return ret

def join(args, unknownargs):
    global admin, password, debug_level, winbindd, smb_conf, net, samba_tool, default_realm
    if len(unknownargs) > 0:
        args.help_func()
        exit(1)
    if getuser() != 'root':
        sys.stderr.write('ads join must be run as root\n')
        exit(1)
    admin, password = get_creds(admin, password)

    # Set the default realm, else kinit will fail
    default_realm = args.domain

    stop_samba()
    stop_disable_service('nscd')

    server = None
    if not args.servers:
        args.servers = list_servers(args.domain)
    server = args.servers[-1]

    stash_config()

    # configure kerberos
    print('Configuring kerberos...')
    config_krb5_conf(args.domain.upper(), server.upper())

    # Configure ntp
    print('Adding ntp servers and time syncing with AD...')
    if args.servers:
        config_ntp(args.servers)
    else:
        config_ntp([server])

    # Update hostname
    print('Updating hostname...')
    hostname = update_hostname(args.n, args.domain)

    if not hasattr(args, 'domain_controller') or not args.domain_controller:
        # configure smb.conf
        print('Configuring smb.conf...')
        config_smb_conf(args.domain, autogen=args.autogen_posix_attrs)

        # configure pam_winbind.conf
        print('Configuring pam_winbind.conf...')
        config_winbind_conf()

        # net ads join the domain
        print('Joining the domain...')
        cmd = [net, '--configfile=%s' % get_smb_conf(), 'ads', 'join', '-U%s%%%s' % (admin, password), '-d', debug_level, '-S', server]
        if debug_level != '0':
            print(' '.join(cmd))
        ret = Popen(cmd).wait()
        if ret != 0:
            return ret

        # start winbindd
        sys.stdout.write('Starting winbindd... ')
        ret = start_enable_service('winbindd', winbindd)
        if ret != 0:
            sys.stdout.write('failed\n')
        else:
            sys.stdout.write('ok\n')
    else:
        if not os.path.exists(samba_tool):
            sys.stderr.write('samba-ad-dc is not installed\n')
            return 1

        # Cleanup old samba database files
        clean_samba_db()

        # Kinit as the admin
        print('kinit as %s...' % admin)
        if kinit_admin(admin, password) != 0:
            print('kinit failed')
        if debug_level != '0':
            Popen(['klist']).wait()

        # samba-tool domain join
        print('Joining the domain as a Domain Controller...')
        cmd = [samba_tool, 'domain', 'join', args.domain, 'DC', '-U%s@%s%%%s' % (admin, args.domain, password)]
        if debug_level != '0':
            print(' '.join(cmd))
        ret = Popen(cmd).wait()
        if ret != 0:
            return ret

        # configure smb.conf
        print('Configuring smb.conf...')
        config_smb_conf(args.domain, server=True)

        # configure pam_winbind.conf
        print('Configuring pam_winbind.conf...')
        config_winbind_conf()

        # start samba
        sys.stdout.write('Starting samba... ')
        ret = start_samba()
        if ret != 0:
            sys.stdout.write('failed\n')
        else:
            sys.stdout.write('ok\n')

        # make sure the A record was added 
        for ip in ip_addrs():
            print('Verifying the DC DNS Record...')
            cmd = ['host', '-t', 'A', hostname[0]]
            if debug_level != '0':
                print(' '.join(cmd))
            ret = Popen(cmd, stdout=PIPE, stderr=PIPE).wait()
            if ret != 0:
                cmd = [samba_tool, 'dns', 'add', server, args.domain, hostname[1], 'A', ip, '-U%s@%s%%%s' % (admin, args.domain, password)]
                if debug_level != '0':
                    print(' '.join(cmd))
                sys.stdout.write('Creating the DC DNS Record... ')
                ret = Popen(cmd, stdout=PIPE, stderr=PIPE).wait()
                if ret != 0:
                    sys.stdout.write('failed\n')
                else:
                    sys.stdout.write('ok\n')

        print('Verifying the objectGUID Record...')
        sleep(3) # Creating the objectGUID dies if we don't sleep a bit
        objectGUID = None
        l = ldap_open(args.domain, admin, password)
        results = l.search_s("CN=Sites,CN=Configuration,%s" % realm_to_dn(args.domain), ldap.SCOPE_SUBTREE, "(invocationId=*)", ["objectguid"])
        for result in results:
            if 'CN=NTDS Settings,CN=%s,' % hostname[1] in result[0]:
                objectGUID = str(uuid.UUID(bytes=result[1]['objectGUID'][-1]))
                break
        if objectGUID:
            cmd = ['host', '-t', 'CNAME', '%s._msdcs.%s' % (objectGUID, args.domain)]
            if debug_level != '0':
                print(' '.join(cmd))
            ret = Popen(cmd).wait()
            if ret != 0:
                cmd = [samba_tool, 'dns', 'add', hostname[1], '_msdcs.%s' % args.domain, objectGUID, 'CNAME', hostname[0], '-U%s@%s%%%s' % (admin, args.domain, password)]
                if debug_level != '0':
                    print(' '.join(cmd))
                sys.stdout.write('Creating the objectGUID Record... ')
                ret = Popen(cmd, stdout=PIPE, stderr=PIPE).wait()
                if ret != 0:
                    sys.stdout.write('failed\n')
                    sys.stderr.write('The objectGUID Record will need to be created manually by running:\n# %s -U%s@%s\n' % (' '.join(cmd[:-1]), admin, args.domain))
                else:
                    sys.stdout.write('ok\n')

        # Piggy back the sysvol off the primary KDC. TODO: Replicate the sysvol for real
        lp = LoadParm()
        lp.load(get_smb_conf())
        cmd = ['mount.cifs', '//%s/sysvol' % server, lp.get('path', 'sysvol'), '-o', 'vers=2.1,username=%s,password=\'%s\'' % (admin, password)]
        if debug_level != '0':
            print(' '.join(cmd[:-1]), ','.join(cmd[-1].split(',')[0:2]))
        sys.stdout.write('Mounting the sysvol share...')
        ret = Popen(cmd, stdout=PIPE, stderr=PIPE).wait()
        if ret != 0:
            sys.stdout.write('failed\n')
            sys.stderr.write('The sysvol will need to be mounted manually by running:\n# %s %s\n' % (' '.join(cmd[:-1]), ','.join(cmd[-1].split(',')[:-1])))
        else:
            sys.stdout.write('ok\n')

    # configure nss
    print('Configuring nsswitch.conf...')
    configure_nss()

    # configure pam
    print('Configuring pam...')
    configure_pam()

def provision(args, unknownargs):
    global admin, password, debug_level, winbindd, smb_conf, net, samba_tool, krb5_conf
    if len(unknownargs) > 0:
        args.help_func()
        exit(1)
    if getuser() != 'root':
        sys.stderr.write('ads join must be run as root\n')
        exit(1)
    if admin:
        print('Changing username to Administrator because this is a provision...')
    admin = 'Administrator'
    if not password:
        password = getpass("%s's Password: " % admin)

    stop_samba()
    stop_disable_service('nscd')

    # Update hostname
    print('Updating hostname...')
    hostname = update_hostname(args.n, args.domain)

    stash_config()
    clean_samba_db()

    print('Provision the domain controller...')
    cmd = [samba_tool, 'domain', 'provision', '--use-rfc2307', '--realm=%s' % args.domain.upper(), '--domain=%s' % args.domain.split('.')[0].upper(), '--server-role=dc', '--dns-backend=SAMBA_INTERNAL']
    if args.bind_interfaces:
        cmd.extend(['--option="interfaces=%s"' % ' '.join(args.bind_interfaces), '--option="bind interfaces only=yes"'])
    cmd.extend(['--adminpass=%s' % password])
    if debug_level != '0':
        print(' '.join(cmd))
    ret = Popen(cmd).wait()
    if ret != 0:
        return ret

    # configure smb.conf
    print('Configuring smb.conf...')
    config_smb_conf(args.domain, server=True)

    # configure pam_winbind.conf
    print('Configuring pam_winbind.conf...')
    config_winbind_conf()

    print('Configuring kerberos...')
    lp = LoadParm()
    lp.load(get_smb_conf())
    cmd = ['ln', '-sf', os.path.join(lp.get('private directory'), 'krb5.conf'), krb5_conf]
    if debug_level != '0':
        print(' '.join(cmd))
    Popen(cmd).wait()

    # start samba
    sys.stdout.write('Starting samba... ')
    ret = start_samba()
    if ret != 0:
        sys.stdout.write('failed\n')
    else:
        sys.stdout.write('ok\n')

    # configure nss
    print('Configuring nsswitch.conf...')
    configure_nss()

    # configure pam
    print('Configuring pam...')
    configure_pam()

def demote(args, unknownargs):
    global admin, password, debug_level, samba_tool, krb5_conf
    if len(unknownargs) > 0:
        args.help_func()
        exit(1)
    if getuser() != 'root':
        sys.stderr.write('ads demote must be run as root\n')
        exit(1)
    admin, password = get_creds(admin, password)

    print('Demoting the ADDC...')
    cmd = [samba_tool, 'domain', 'demote', '-d', debug_level, '-U%s%%%s' % (admin, password)]
    if debug_level != '0':
        print(' '.join(cmd[:-1]), '-U%s' % admin)
    Popen(cmd).wait()

    lp = LoadParm()
    lp.load(get_smb_conf())
    print('Unmounting the sysvol...')
    Popen(['umount', lp.get('path', 'sysvol')]).wait()

    print('Disabling the samba service...')
    stop_disable_service('samba')
    stop_samba()

    print('Unconfiguring pam...')
    unconfigure_pam()

    print('Unconfiguring nss...')
    unconfigure_nss()

    # Cleanup old samba database files
    print('Deleting samba database files...')
    clean_samba_db()

    if os.path.exists(get_smb_conf()):
        print('Deleting smb.conf...')
        os.remove(get_smb_conf())
    if os.path.exists(krb5_conf):
        print('Deleting krb5.conf...')
        os.remove(krb5_conf)


def unjoin(args, unknownargs):
    global admin, password, debug_level, net, krb5_conf
    if len(unknownargs) > 0:
        args.help_func()
        exit(1)
    lp = LoadParm()
    lp.load(get_smb_conf())
    if lp.get('server role') == 'active directory domain controller':
        demote(args, unknownargs)
    else:
        if getuser() != 'root':
            sys.stderr.write('ads unjoin must be run as root\n')
            exit(1)
        admin, password = get_creds(admin, password)

        print('Unjoining the domain...')
        cmd = [net, 'ads', 'leave', '-d', debug_level, '-U%s%%%s' % (admin, password)]
        if debug_level != '0':
            print(' '.join(cmd[:-1]), '-U%s' % admin)
        Popen(cmd).wait()

        remove_hosts_config()

        stop_disable_service('winbindd')

        stop_samba()

        print('Unconfiguring pam...')
        unconfigure_pam()

        print('Unconfiguring nss...')
        unconfigure_nss()

        # Cleanup old samba database files
        clean_samba_db()

        if os.path.exists(get_smb_conf()):
            print('Deleting smb.conf...')
            os.remove(get_smb_conf())
        if os.path.exists(krb5_conf):
            print('Deleting krb5.conf...')
            os.remove(krb5_conf)

def info(args, unknownargs):
    if len(unknownargs) > 0:
        args.help_func()
        exit(1)
    if args.s2 == 'domain':
        print(get_default_realm())

def kcc(cmd):
    if debug_level != '0':
        print(' '.join(cmd))
    try:
        return Popen(cmd).wait()
    except OSError as e:
        if 'No such file or directory' in str(e):
            sys.stderr.write('%s is not in your path\n' % cmd[0])
            exit(1)

def kinit(args, unknownargs):
    cmd = ['kinit']
    cmd.extend(unknownargs)
    kcc(cmd)

def klist(args, unknownargs):
    cmd = ['klist']
    cmd.extend(unknownargs)
    kcc(cmd)

def kdestroy(args, unknownargs):
    cmd = ['kdestroy']
    cmd.extend(unknownargs)
    kcc(cmd)

def ktutil(args, unknownargs):
    cmd = ['ktutil']
    cmd.extend(unknownargs)
    kcc(cmd)

def flush(args, unknownargs):
    global net
    if len(unknownargs) > 0:
        args.help_func()
        exit(1)
    if getuser() != 'root':
        sys.stderr.write('ads flush must be run as root\n')
        exit(1)
    stop_samba()

    cmd = [net, 'cache', 'flush']
    if debug_level != '0':
        print(' '.join(cmd))
    Popen(cmd).wait()

    lp = LoadParm()
    lp.load(get_smb_conf())
    if lp.get('server role') == 'active directory domain controller':
        start_samba()
    else:
        start_enable_service('winbindd', winbindd)

def daemon(args, unknownargs):
    if len(unknownargs) > 0:
        args.help_func()
        exit(1)
    if getuser() != 'root':
        sys.stderr.write('ads daemon must be run as root\n')
        exit(1)
    binary = None
    if args.service == 'winbindd':
        binary = winbindd
    elif args.service == 'samba':
        binary = samba
    if args.action == 'start':
        return start_enable_service(args.service, binary)
    elif args.action == 'stop':
        stop_samba(args.service)
    elif args.action == 'restart':
        stop_samba(args.service)
        return start_enable_service(args.service, binary)

def argparser():
    description = "Active Directory Swiss army knife for samba.\nFor join, unjoin, provisioning, demotion, user/group and password administration,\nldap attribute modification, posix enablement, kdc timesync, pam and nss configuration,\ndaemon start/stop, cache flush, etc.\nThe ads command attempts to maintain compatibility with the proprietary vastool command,\nwhile also adding additional features relevant to samba (such as kdc provisioning)."
    parser = argparse.ArgumentParser(description=description, formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument('-v', '--version', action='version', version='ads 1.5', help='ads version')
    parser.add_argument('--pam_auth_conf', help='Specify the location of the pam auth conf file')
    parser.add_argument('--pam_password_conf', help='Specify the location of the pam password file')
    parser.add_argument('-u', help='Authenticating user')
    parser.add_argument('-w', help='Authenticating password')
    parser.add_argument('-d', help='debug level')
    subparsers = parser.add_subparsers()

    nss_parser = subparsers.add_parser('nss', help='Run nss functions')
    nss_parser.add_argument('s2', choices=['getpwnam', 'getpwuid', 'getgrnam', 'getgrid', 'getpwent', 'getgrent'], help='')
    nss_parser.add_argument('-d', '--direct', help='bypass the nss layer and return results directly from ldap', action='store_true')
    nss_parser.add_argument('object', nargs='?')
    nss_parser.set_defaults(func=nss)
    nss_parser.set_defaults(help_func=nss_parser.print_help)

    user_parser = subparsers.add_parser('user', help='Manage Active Directory users')
    user_parser.add_argument('s2', choices=['checklogin', 'checkaccess'])
    user_parser.add_argument('user', nargs='?')
    user_parser.set_defaults(func=user)
    user_parser.set_defaults(help_func=user_parser.print_help)

    create_parser = subparsers.add_parser('create', help='Create Active Directory users/groups')
    create_parser.add_argument('-e', help='Operate on an existing object', action='store_true')
    create_parser.add_argument('-i', help='Passwd line for unix enable')
    create_parser.add_argument('-c', help='Container to search in')
    create_parser.add_argument('s2', choices=['user', 'group'])
    create_parser.add_argument('object', nargs='?')
    create_parser.set_defaults(func=create)
    create_parser.set_defaults(help_func=create_parser.print_help)

    delete_parser = subparsers.add_parser('delete', help='Delete Active Directory users/groups')
    delete_parser.add_argument('s2', choices=['user', 'group'])
    delete_parser.add_argument('object', nargs='?')
    delete_parser.set_defaults(func=delete)
    delete_parser.set_defaults(help_func=delete_parser.print_help)

    list_parser = subparsers.add_parser('list', help='List Active Directory users/groups')
    list_parser.add_argument('s2', choices=['users', 'groups'])
    list_parser.set_defaults(func=nss)
    list_parser.set_defaults(help_func=list_parser.print_help)

    passwd_parser = subparsers.add_parser('passwd', help='Change Active Directory user passwords')
    passwd_parser.add_argument('object', nargs='?')
    passwd_parser.set_defaults(func=passwd)
    passwd_parser.set_defaults(help_func=passwd_parser.print_help)

    attrs_parser = subparsers.add_parser('attrs', help='List Active Directory object attributes')
    attrs_parser.add_argument('-b', action='store_true', help='Convert sid to human readable form')
    attrs_parser.add_argument('-c', help='Container to search in')
    attrs_parser.add_argument('-g', help='Treat the object as a group name', action='store_true')
    attrs_parser.add_argument('object')
    attrs_parser.add_argument('attributes', nargs='*')
    attrs_parser.set_defaults(func=attrs)
    attrs_parser.set_defaults(help_func=attrs_parser.print_help)

    setattrs_parser = subparsers.add_parser('setattrs', help='Modify Active Directory object attributes')
    setattrs_parser.add_argument('object', help='distinguishedName')
    setattrs_parser.add_argument('attribute')
    setattrs_parser.add_argument('value')
    setattrs_parser.set_defaults(func=setattrs)
    setattrs_parser.set_defaults(help_func=setattrs_parser.print_help)

    join_parser = subparsers.add_parser('join', help='Join this computer to an Active Directory domain')
    join_parser.add_argument('--autogen-posix-attrs', action='store_true')
    if kdc_installed():
        join_parser.add_argument('--domain-controller', action='store_true', help='Join the machine as a Active Directory Domain Controller member server')
    join_parser.add_argument('--disable-pam', action='store_true', help='Don\'t configure pam during the join')
    join_parser.add_argument('domain')
    join_parser.add_argument('-n', help='Join as hostname')
    join_parser.add_argument('servers', nargs='*')
    join_parser.set_defaults(func=join)
    join_parser.set_defaults(help_func=join_parser.print_help)

    unjoin_parser = subparsers.add_parser('unjoin', help='Unjoin this computer from the Active Directory domain')
    unjoin_parser.set_defaults(func=unjoin)
    unjoin_parser.set_defaults(help_func=unjoin_parser.print_help)

    if kdc_installed():
        provision_parser = subparsers.add_parser('provision', help='Provision an Active Directory Domain Controller')
        provision_parser.add_argument('domain')
        provision_parser.add_argument('-n', help='Join as hostname')
        provision_parser.add_argument('--bind-interfaces', nargs='+', help='Use this option to bind Samba to the specified interfaces')
        provision_parser.set_defaults(func=provision)
        provision_parser.set_defaults(help_func=provision_parser.print_help)

        demote_parser = subparsers.add_parser('demote', help='Demote an Active Directory Domain Controller')
        demote_parser.set_defaults(func=demote)
        demote_parser.set_defaults(help_func=demote_parser.print_help)

    timesync_parser = subparsers.add_parser('timesync', help='Syncronize machine time with Active Directory')
    timesync_parser.add_argument('-s', help='Server to sync with')
    timesync_parser.set_defaults(func=timesync)
    timesync_parser.set_defaults(help_func=timesync_parser.print_help)

    configure_parser = subparsers.add_parser('configure', help='Configure pam and nss for winbind authentication')
    configure_parser.add_argument('s2', choices=['pam', 'nss'])
    configure_parser.set_defaults(func=configure)
    configure_parser.set_defaults(help_func=configure_parser.print_help)

    unconfigure_parser = subparsers.add_parser('unconfigure', help='Unconfigure pam and nss for winbind authentication')
    unconfigure_parser.add_argument('s2', choices=['pam', 'nss'])
    unconfigure_parser.set_defaults(func=unconfigure)
    unconfigure_parser.set_defaults(help_func=unconfigure_parser.print_help)

    info_parser = subparsers.add_parser('info', help='Get information about the domain')
    info_parser.add_argument('s2', choices=['domain'])
    info_parser.set_defaults(func=info)
    info_parser.set_defaults(help_func=info_parser.print_help)

    kinit_parser = subparsers.add_parser('kinit', help='Request an initial ticket-granting ticket')
    kinit_parser.set_defaults(func=kinit)
    kinit_parser.set_defaults(help_func=kinit_parser.print_help)

    klist_parser = subparsers.add_parser('klist', help='Lists the Kerberos principal and Kerberos tickets held in a  credentials  cache')
    klist_parser.set_defaults(func=klist)
    klist_parser.set_defaults(help_func=klist_parser.print_help)

    kdestroy_parser = subparsers.add_parser('kdestroy', help='Destroys the user\'s active Kerberos authorization tickets')
    kdestroy_parser.set_defaults(func=kdestroy)
    kdestroy_parser.set_defaults(help_func=kdestroy_parser.print_help)

    ktutil_parser = subparsers.add_parser('ktutil', help='Invokes a command interface from which an administrator can read, write, or edit entries in a keytab')
    ktutil_parser.set_defaults(func=ktutil)
    ktutil_parser.set_defaults(help_func=ktutil_parser.print_help)

    flush_parser = subparsers.add_parser('flush', help='Deletes all cache entries')
    flush_parser.set_defaults(func=flush)
    flush_parser.set_defaults(help_func=flush_parser.print_help)

    daemon_parser = subparsers.add_parser('daemon', help='Start, stop or restart the samba or winbind service')
    daemon_parser.add_argument('action', choices=['start', 'stop', 'restart'])
    daemon_parser.add_argument('service', choices=['winbindd', 'samba'])
    daemon_parser.set_defaults(func=daemon)
    daemon_parser.set_defaults(help_func=daemon_parser.print_help)

    return parser

if __name__ == "__main__":
    parser = argparser()
    args, unknownargs = parser.parse_known_args()

    if args.pam_auth_conf:
        pam_auth_conf = args.pam_auth_conf
    if args.pam_password_conf:
        pam_password_conf = args.pam_password_conf

    if args.u:
        admin = args.u
    if args.w:
        password = args.w
    if args.d:
        debug_level = args.d

    exit(args.func(args, unknownargs))

