#!/usr/bin/env python

#       Licensed to the Apache Software Foundation (ASF) under one
#       or more contributor license agreements.  See the NOTICE file
#       distributed with this work for additional information
#       regarding copyright ownership.  The ASF licenses this file
#       to you under the Apache License, Version 2.0 (the
#       "License"); you may not use this file except in compliance
#       with the License.  You may obtain a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#       Unless required by applicable law or agreed to in writing,
#       software distributed under the License is distributed on an
#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#       KIND, either express or implied.  See the License for the
#       specific language governing permissions and limitations
#       under the License.

import os
import shutil
import string
import logging
from contextlib import contextmanager
from tempfile import mkstemp
from configparser import ConfigParser, NoOptionError

logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger('ldap-setup')

config = ConfigParser()


def main():
    config.read('.setup-scm-cache')
    if not config.has_section('scm'):
        config.add_section('scm')
    suffix = get_value('suffix', 'dc=localdomain')
    secret = get_value('admin password', 'secret')
    firstdc = suffix.split(',')[0].split('=')[1]
    if get_value('clear ldap config', 'y') == 'y':
        run('apt-get -f install')
        run('apt-get remove --purge slapd ldap-utils')
        run('apt-get install slapd ldap-utils')
    if get_value('start slapd', 'y') == 'y':
        run('service slapd start')
    if get_value('add base ldap schemas', 'y') == 'y':
        run('ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/cosine.ldif')
        run('ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/nis.ldif')
        run('ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/inetorgperson.ldif')
    if get_value('add backend ldif', 'y') == 'y':
        with tempfile(backend_ldif, locals()) as name:
            run('ldapadd -Y EXTERNAL -H ldapi:/// -f %s' % name)
    with open('/etc/ldap.secret', 'w') as fp:
        fp.write(secret)
    os.chmod('/etc/ldap.secret', 0o400)
    if get_value('add frontend ldif', 'y') == 'y':
        with tempfile(frontend_ldif, locals()) as name:
            run('ldapadd -c -x -D cn=admin,%s -W -f %s -y /etc/ldap.secret' %
                (suffix, name))
    if get_value('add initial user/group', 'y') == 'y':
        with tempfile(initial_user_ldif, locals()) as name:
            run('ldapadd -c -x -D cn=admin,%s -W -f %s -y /etc/ldap.secret' %
                (suffix, name))
    if get_value('setup ldap auth', 'y') == 'y':
        run('apt-get install libnss-ldap')
        run('dpkg-reconfigure ldap-auth-config')
        run('auth-client-config -t nss -p lac_ldap')
        run('pam-auth-update')
    if get_value('setup ldapscripts', 'y') == 'y':
        run('apt-get install ldapscripts')
        with tempfile(ldapscripts_conf, locals()) as name:
            shutil.copy(name, '/etc/ldapscripts/ldapscripts.conf')
        log.info('writing passwd')
        with open('/etc/ldapscripts/ldapscripts.passwd', 'w') as fp:
            fp.write(secret)
        os.chmod('/etc/ldapscripts/ldapscripts.passwd', 0o400)
        log.info('writing runtime')
        with open('/usr/share/ldapscripts/runtime.debian', 'w') as fp:
            fp.write(ldapscripts_debian)


def get_value(key, default):
    try:
        default = config.get('scm', key)
    except NoOptionError:
        pass
    value = input(f'{key}? [{default}]')
    if not value:
        value = default
    config.set('scm', key, value)
    with open('.setup-scm-cache', 'w') as fp:
        config.write(fp)
    return value


def run(command):
    rc = os.system(command)  # noqa: S605
    if rc != 0:
        log.error('Error running %s', command)
    assert rc == 0
    return rc


@contextmanager
def tempfile(template, values):
    fd, name = mkstemp()
    os.write(fd, template.safe_substitute(values))
    os.close(fd)
    yield name
    os.remove(name)


backend_ldif = string.Template('''
# Load dynamic backend modules
dn: cn=module,cn=config
objectClass: olcModuleList
cn: module
olcModulepath: /usr/lib/ldap
olcModuleload: back_hdb

# Database settings
dn: olcDatabase=hdb,cn=config
objectClass: olcDatabaseConfig
objectClass: olcHdbConfig
olcDatabase: {1}hdb
olcSuffix: $suffix
olcDbDirectory: /var/lib/ldap
olcRootDN: cn=admin,$suffix
olcRootPW: $secret
olcDbConfig: set_cachesize 0 2097152 0
olcDbConfig: set_lk_max_objects 1500
olcDbConfig: set_lk_max_locks 1500
olcDbConfig: set_lk_max_lockers 1500
olcDbIndex: objectClass eq
olcLastMod: TRUE
olcDbCheckpoint: 512 30
olcAccess: to attrs=userPassword by dn="cn=admin,$suffix" write by anonymous auth by self write by * none
olcAccess: to attrs=shadowLastChange by self write by * read
olcAccess: to dn.base="" by * read
olcAccess: to * by dn="cn=admin,$suffix" write by * read

''')

frontend_ldif = string.Template('''
# Create top-level object in domain
dn: $suffix
objectClass: top
objectClass: dcObject
objectclass: organization
o: Example Organization
dc: $firstdc
description: LDAP Example

# Create max uid generator
dn: cn=maxUid,$suffix
objectClass: extensibleObject
objectClass: top
uidNumber: 10000

# Admin user.
dn: cn=admin,$suffix
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator
userPassword: $secret

dn: ou=people,$suffix
objectClass: organizationalUnit
ou: people

dn: ou=groups,$suffix
objectClass: organizationalUnit
ou: groups
''')

initial_user_ldif = string.Template('''
dn: uid=john,ou=people,$suffix
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: john
sn: Doe
givenName: John
cn: John Doe
displayName: John Doe
uidNumber: 1000
gidNumber: 10000
userPassword: password
gecos: John Doe
loginShell: /bin/bash
homeDirectory: /home/john
shadowExpire: -1
shadowFlag: 0
shadowWarning: 7
shadowMin: 8
shadowMax: 999999
shadowLastChange: 10877
mail: john.doe@example.com
postalCode: 31000
l: Toulouse
o: Example
mobile: +33 (0)6 xx xx xx xx
homePhone: +33 (0)5 xx xx xx xx
title: System Administrator
postalAddress:
initials: JD

dn: cn=example,ou=groups,$suffix
objectClass: posixGroup
cn: example
gidNumber: 10000
''')

open_ldap_config = string.Template('''
[open_ldap]
nss_passwd=passwd: files ldap
nss_group=group: files ldap
nss_shadow=shadow: files ldap
nss_netgroup=netgroup: files ldap
pam_auth=auth       required     pam_env.so
        auth       sufficient   pam_unix.so likeauth nullok
#the following line (containing pam_group.so) must be placed before pam_ldap.so
#for ldap users to be placed in local groups such as fuse, plugdev, scanner, etc ...
        auth       required     pam_group.so use_first_pass
        auth       sufficient   pam_ldap.so use_first_pass
        auth       required     pam_deny.so
pam_account=account    sufficient   pam_unix.so
        account    sufficient   pam_ldap.so
        account    required     pam_deny.so
pam_password=password   sufficient   pam_unix.so nullok md5 shadow
        password   sufficient   pam_ldap.so use_first_pass
        password   required     pam_deny.so
pam_session=session    required     pam_limits.so
        session    required     pam_mkhomedir.so skel=/etc/skel/
        session    required     pam_unix.so
        session    optional     pam_ldap.so
''')

ldapscripts_conf = string.Template('''
SERVER=127.0.0.1
BINDDN='cn=admin,$suffix'
BINDPWDFILE="/etc/ldapscripts/ldapscripts.passwd"
SUFFIX='$suffix'
GSUFFIX='ou=Groups'
USUFFIX='ou=People'
MSUFFIX='ou=Computers'
GIDSTART=10000
UIDSTART=10000
MIDSTART=10000
''')


ldapscripts_debian = r'''
### Allura-customized
### This file predefine some ldapscripts variables for Debian boxes.
#
#  Copyright (c) 2005 Ganal LAPLANCHE - Linagora
#  Copyright (c) 2005-2007 Pierre Habouzit
#  Copyright (c) 2009 Alexander GQ Gerasiov
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
#  USA.

##### Beginning of ldapscripts configuration #####

getfield() {
    local field="$1"
    local nssconffile='/etc/libnss-ldap.conf'
    if [ -f "$nssconffile" ];then
        local value=$(awk "/^\s*$field/ {print \$2}" /etc/libnss-ldap.conf)
    else
        local value="$2"
    fi
    echo ${value:-$2}
}

getsuffix() {
    field="$1"
    value="$(getfield "$1" | sed -e "s/,.*$//")"
    echo ${value:-$2}
}

# LDAP Configuration
SERVER=$(getfield uri "$(getfield host '')")
BINDDN=$(getfield rootbinddn '')
if [ -f /etc/libnss-ldap.secret ];then
        BINDPWDFILE=/etc/libnss-ldap.secret
elif [ -f /etc/ldap.secret ];then
        BINDPWDFILE=/etc/ldap.secret
fi

SUFFIX=`getfield base`
GSUFFIX=`getsuffix nss_base_group   'ou=Group'`
USUFFIX=`getsuffix nss_base_passwd  'ou=People'`
MSUFFIX=`getsuffix nss_base_hosts   'ou=Hosts'`

# User properties
[ -f /etc/adduser.conf ] && . /etc/adduser.conf
USHELL=${DSHELL:-"/bin/bash"}
UHOMES=${DHOME:-"/home"}"/%u"
HOMESKEL=${SKEL:-"/etc/skel"}
HOMEPERMS=${DIR_MODE:-"0755"}


# Where to log
LOGFILE="/var/log/ldapscripts.log"

# Various binaries used within scripts
LDAPSEARCHBIN=`which ldapsearch`
LDAPADDBIN=`which ldapadd`
LDAPDELETEBIN=`which ldapdelete`
LDAPMODIFYBIN=`which ldapmodify`
LDAPMODRDNBIN=`which ldapmodrdn`
LDAPPASSWDBIN=`which ldappasswd`

# Getent command to use - choose the ones used on your system. Leave blank or comment for auto-guess.
# GNU/Linux
GETENTPWCMD="getent passwd"
GETENTGRCMD="getent group"


TMPDIR="/tmp"
##### End of configuration #####
'''
if __name__ == '__main__':
    main()
