#!/usr/bin/python3
import os
import ldap, ldap.sasl, ldap.modlist
import urllib.parse 
import crypt
import subprocess
import yaml
from time import time

## global defaults
configFile = '/storage/initialconfig.yaml'
clientConfigFile = '/storage/ldap/etc/ldap/ldap.conf'
nssConfigFile = '/storage/ldap/etc/libnss-ldap.conf'
nssConfigSecretFile = '/storage/ldap/etc/libnss-ldap.secret'
sambaConfigFile = '/storage/samba/etc/smb.conf'
defaultHomeDir = '/home'
config = yaml.safe_load(open(configFile, 'r').read())


## create TLS certificates if not existing
fullDomainName = config['top']['domain'].strip().lower()
suffix = 'dc=' + fullDomainName.replace('.', ',dc=')
subprocess.run(['/usr/local/bin/generate-certificate', fullDomainName])

# connect to LDAP using SASL and the root account
c = ldap.initialize('ldapi:///run/ldapi')
c.sasl_non_interactive_bind_s('EXTERNAL')

## setup initial domain and admin password
salt = crypt.mksalt()
cryptPassword = crypt.crypt(config['top']['adminpassword'], salt)
noneValue = ['None'.encode('utf-8')]
adminDN = 'cn=admin,' + suffix
old = {
    'olcRootPw' : noneValue,
    'olcSuffix' : noneValue,
    'olcRootDN' : noneValue
}
new = {
    'olcRootPw' : [('{CRYPT}%s' % cryptPassword).encode('utf-8')],
    'olcSuffix' : [suffix.encode('utf-8')],
    'olcRootDN' : [adminDN.encode('utf-8')]
}
ldif = ldap.modlist.modifyModlist(old,new)
dn = 'olcDatabase={1}mdb,cn=config'
c.modify_s(dn,ldif)
c.unbind_s()

# we need to use the admin DN instead of SASL + root now
c = ldap.initialize('ldap://localhost')
c.simple_bind_s(adminDN, config['top']['adminpassword'])

## setup dcObjectf
domainControllerName = fullDomainName.split('.')[0]
add = {
    'dc'            :[domainControllerName.encode('utf-8')],
    'o'             : [config['top']['description'].encode('utf-8')],
    'objectClass'   : [b'organization', b'dcObject'],
}
ldif = ldap.modlist.addModlist(add)
c.add_s(suffix, ldif)

## setup OUs for people and groups
peopleOU = 'ou=people,' + suffix
add = {
        'ou'            : [b'people'],    
        'objectClass'   : [b'organizationalUnit'], 
}
ldif = ldap.modlist.addModlist(add)
c.add_s(peopleOU, ldif)

groupsOU = 'ou=groups,' + suffix
add = {
        'ou'            : [b'groups'],    
        'objectClass'   : [b'organizationalUnit'], 
}
ldif = ldap.modlist.addModlist(add)
c.add_s(groupsOU, ldif)

computersOU = 'ou=computers,' + suffix
add = {
        'ou'            : [b'computers'],    
        'objectClass'   : [b'organizationalUnit'], 
}
ldif = ldap.modlist.addModlist(add)
c.add_s(computersOU, ldif)

idmapOU = 'ou=idmap,' + suffix
add = {
        'ou'            : [b'idmap'],    
        'objectClass'   : [b'organizationalUnit'], 
}
ldif = ldap.modlist.addModlist(add)
c.add_s(idmapOU, ldif)


## setup users
userDNs = {}
for user in config['users']:

    # create POSIX user
    if user['enabled'] and 'password' in user:
        userDN = 'uid=%s,%s' % (user['uid'], peopleOU)
        userDNs[user['uid']] = userDN
        salt = crypt.mksalt()
        userPassword = crypt.crypt(user['password'], salt)
        displayName = '%s %s' % (user['givenName'], user['sirName'])
        add = {
            'uid'           : [user['uid'].encode('utf-8')],
            'cn'            : [displayName.encode('utf-8')],
            'sn'            : [user['sirName'].encode('utf-8')],
            'gecos'         : [('%s' % userDN).encode('utf-8')], 
            'givenName'     : [user['givenName'].encode('utf-8')],
            'displayName'   : [displayName.encode('utf-8')],
            'mail'          : [user['mail'].encode('utf-8')],
            'uidNumber'     : [str(user['uidNumber']).encode('utf-8')],
            'gidNumber'     : [str(user['gidNumber']).encode('utf-8')],
            'homeDirectory' : [(defaultHomeDir + os.sep + user['uid']).encode('utf-8')],
            'loginShell'    : [user['shell'].encode('utf-8')],
            'userPassword'  : [('{CRYPT}' + userPassword).encode('utf-8')],
            'objectClass'   : [b'inetOrgPerson', b'person', b'posixAccount', b'shadowAccount'],
            'shadowLastChange' : [str(int(time() / (60 * 60 * 24))).encode('utf-8')],
            'shadowMin'     : [b'0'],
            'shadowMax'     : [b'99999'],
            'shadowWarning' : [b'7'],
            'shadowInactive': [b'-1'],
            'shadowExpire'  : [b'-1'],
            'shadowFlag'    : [b'-1']
        }
        ldif = ldap.modlist.addModlist(add)
        print('User for "%s" (uid %i) has been created' % (displayName, user['uidNumber']))
        c.add_s(userDN, ldif)

    # create user group for POSIX user
        groupDN = 'cn=%s,%s' % (user['uid'], groupsOU)
        add = {
            'cn'            : [user['uid'].encode('utf-8')],
            'gidNumber'     : [str(user['gidNumber']).encode('utf-8')],
            'member'        : [('uid=%s,%s' % (user['uid'], peopleOU)).encode('utf-8')],
            'description'   : [('%s' % groupDN).encode('utf-8')],
            'objectClass'   : [b'groupOfNames', b'posixGroup']
        }
        ldif = ldap.modlist.addModlist(add)
        c.add_s(groupDN, ldif)
        print('User group for "%s" (gid %i) has been created' % (displayName, user['gidNumber']))


## setup groups
for group in config['groups']:
    groupDN = 'cn=%s,%s' % (group['name'], groupsOU)
    memberList = []
    for userUid in group['members']:
        if userUid in userDNs:
            userDN = (userDNs[userUid]).encode('utf-8')
            memberList.append(userDN)

    add = {
        'cn'            :  [group['name'].encode('utf-8')],
        'gidNumber'     :  [str(group['gidNumber']).encode('utf-8')],
        'member'        : [('uid=%s,%s' % (user['uid'], peopleOU)).encode('utf-8')],
        'objectClass'   :  [b'groupOfNames', b'posixGroup'],
        'member'        :  memberList
    }
    ldif = ldap.modlist.addModlist(add)
    c.add_s(groupDN, ldif)
    print('Group "%s" (gid %i) has been created' % (group['name'], group['gidNumber']))

c.unbind_s()

## create configuration file
configString = """
base {Base}
uri ldaps://{FQDN}/
ldap_version 3
rootbinddn {AdminDN}
bind_policy soft
pam_filter objectclass=posixAccount
pam_password crypt
nss_base_passwd     {PeopleOU}?one
nss_base_shadow     {PeopleOU}?one
nss_base_group      {GroupsOU}?one
nss_map_attribute uniqueMember member
""".format(**{
    'Base'          :   suffix,
    'AdminDN'       :   adminDN,
    'PeopleOU'      :   peopleOU,
    'GroupsOU'      :   groupsOU,
    'FQDN'          :   fullDomainName
})


with open(nssConfigFile, 'w') as f:
    f.write(configString)

with open(nssConfigSecretFile, 'w') as f:
    f.write(config['top']['adminpassword'])


ldapClientConfig = """
TLS_CACERT  /etc/ssl/certs/ca-certificates.crt
TLS_REQCERT allow
BASE        {Base}
URI         ldaps://{FQDN}:636
""".format(**{
    'Base'          :   suffix,
    'FQDN'          :   fullDomainName
})

with open(clientConfigFile, 'w') as f:
    f.write(ldapClientConfig)

os.chmod(nssConfigFile, 0o644)
os.chmod(nssConfigSecretFile, 0o600)
os.chmod(clientConfigFile, 0o644)


# creating samba default configuration
smbConfig = """
[global]
   wins support = yes
   dns proxy = yes
   workgroup = SAMBA
   logging = syslog
   panic action = /usr/share/samba/panic-action %d
   server role = standalone server
   obey pam restrictions = yes
   unix password sync = yes
   passwd program = /usr/bin/passwd %u
   #passwd chat = *Enter\\snew\\s*\\spassword:* %n\\n *Retype\\snew\\s*\\spassword:* %n\\n *password\\supdated\\ssuccessfully* .
   passwd chat = *New\\spassword:* %n\\n *Re-enter\\snew\\spassword:* %n\\n *passwd:\\spassword\\supdated\\ssuccessfully* .
   pam password change = yes
   map to guest = bad user
   usershare allow guests = yes
   load printers = no
   disable spoolss = yes
   socket options = TCP_NODELAY SO_RCVBUF=524288 SO_SNDBUF=524288 IPTOS_LOWDELAY
   passdb backend = ldapsam:ldap://{FQDN}
   ldap suffix = {Base}
   ldap user suffix = ou=people
   ldap group suffix = ou=groups
   ldap machine suffix = ou=computers
   ldap idmap suffix = ou=idmap
   ldap admin dn = {AdminDN}
   ldap ssl = start tls
   ldap passwd sync = yes
 
   tls enabled  = yes
   tls keyfile  = /storage/certificates/privkey.pem
   tls certfile = /storage/certificates/cert.pem
   tls cafile   = 


[homes]
   comment = Home Directories
   browseable = no
   read only = no
   create mask = 0644
   directory mask = 0770
   valid users = %S

""".format(**{
    'Base'          :   suffix,
    'AdminDN'       :   adminDN,
    'PeopleOU'      :   peopleOU,
    'GroupsOU'      :   groupsOU,
    'FQDN'          :   fullDomainName,
#    'PDCN'          :   domainControllerName
})

with open(sambaConfigFile, 'w') as f:
    f.write(smbConfig)

print('Configuration files have been deployed')

print('Deploy indices for SAMBA schema')
subprocess.run(['ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f /tmp/samba-indices.ldif'], shell = True)

print('Restarting LDAP server')
subprocess.run(['/etc/init.d/slapd stop; pkill slapd; /etc/init.d/slapd start'], shell = True)
