# --- BEGIN COPYRIGHT BLOCK ---
# Copyright (C) 2019, William Brown <william@blackhats.net.au>
# All rights reserved.
#
# License: GPL (version 3 or any later version).
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---

from lib389.idm.user import nsUserAccounts
from lib389.idm.group import Groups
from lib389.plugins import MemberOfPlugin
from lib389.utils import basedn_to_ldap_dns_uri

SSSD_CONF_TEMPLATE = """
#
# sssd.conf
# Generated by 389 Directory Server - dsidm
#
# For more details see man sssd.conf and man sssd-ldap
# Be sure to review the content of this file to ensure it is secure and correct
# in your environment.

[domain/ldap]
# Uncomment this for more verbose logging.
# debug_level=3

# Cache hashes of user authentication for offline auth.
cache_credentials = True
id_provider = ldap
auth_provider = ldap
access_provider = ldap
chpass_provider = ldap
ldap_schema = {schema_type}
ldap_search_base = {basedn}
ldap_uri = {ldap_uri}
# If you have DNS SRV records, you can use the following instead. This derives
# from your ldap_search_base.
# ldap_uri = _srv_

ldap_tls_reqcert = demand
# To use cacert dir, place *.crt files in this path then run:
# /usr/bin/openssl rehash /etc/openldap/certs
# or (for older versions of openssl)
# /usr/bin/c_rehash /etc/openldap/certs
ldap_tls_cacertdir = /etc/openldap/certs

# Path to the cacert
# ldap_tls_cacert = /etc/openldap/certs/ca.crt

# Only users who match this filter can login and authorise to this machine. Note
# that users who do NOT match, will still have their uid/gid resolve, but they
# can't login.
{ldap_access_filter}

enumerate = false
access_provider = ldap
ldap_user_member_of = memberof
ldap_user_gecos = cn
ldap_user_uuid = nsUniqueId
ldap_group_uuid = nsUniqueId
# This is really important as it allows SSSD to respect nsAccountLock
ldap_account_expire_policy = rhds
ldap_access_order = filter, expire
# Setup for ssh keys
# Inside /etc/ssh/sshd_config add the lines:
#   AuthorizedKeysCommand /usr/bin/sss_ssh_authorizedkeys
#   AuthorizedKeysCommandUser nobody
# You can test with the command: sss_ssh_authorizedkeys <username>
ldap_user_ssh_public_key = nsSshPublicKey

# This prevents an issue where the Directory is recursively walked on group
# and user look ups. It makes the client faster and more responsive in almost
# every scenario.
ignore_group_members = False

[sssd]
services = nss, pam, ssh, sudo
config_file_version = 2

domains = ldap
[nss]
homedir_substring = /home

"""

def sssd_conf(inst, basedn, log, args):

    schema_type = "rfc2307"
    try:
        mo_plugin = MemberOfPlugin(inst)
        if mo_plugin.status():
            schema_type = "rfc2307bis"
    except:
        schema_type = "unknown - likely access denied to memberof plugin config"

    ldap_access_filter = "# ldap_access_filter = (memberOf=<dn>)"
    if args.allowed_group:
        groups = Groups(inst, basedn)
        g_access = groups.get(args.allowed_group)
        ldap_access_filter = 'ldap_access_filter = (memberOf=%s)' % g_access.dn

    if inst.ldapuri.startswith("ldapi://"):
        log.warning("WARNING: ldap_uri starts with ldapi:// - you should review this parameter in the sssd configuration")

    # Print a customised sssd.config.
    print(SSSD_CONF_TEMPLATE.format(
        basedn=basedn,
        schema_type=schema_type,
        ldap_uri=inst.ldapuri,
        ldap_access_filter=ldap_access_filter,
    ))

    # Print a customised sssd.conf to log for test purpose
    log.debug(SSSD_CONF_TEMPLATE.format(
        basedn=basedn,
        schema_type=schema_type,
        ldap_uri=inst.ldapuri,
        ldap_access_filter=ldap_access_filter))

LDAP_CONF_TEMPLATE = """
#
# OpenLDAP client configuration
# Generated by 389 Directory Server - dsidm
#

# See ldap.conf(5) for details
# This file should be world readable but not world writable.

BASE    {basedn}
# Remember to check this: you can have multiple uris on this line. You may have
# multiple servers or load balancers in your environment.
URI     {ldap_uri}
# If you have DNS SRV records you can use:
# URI   {ldap_dns_uri}

DEREF   never
# To use cacert dir, place *.crt files in this path then run:
# /usr/bin/openssl rehash /etc/openldap/certs
# or (for older versions of openssl)
# /usr/bin/c_rehash /etc/openldap/certs
TLS_CACERTDIR /etc/openldap/certs
# TLS_CACERT /etc/openldap/certs/ca.crt

"""

def ldap_conf(inst, basedn, log, args):
    # Print a customised ldap.conf or ldaprc
    print(LDAP_CONF_TEMPLATE.format(
        basedn=basedn,
        ldap_uri=inst.ldapuri,
        ldap_dns_uri=basedn_to_ldap_dns_uri(basedn),
    ))

    # Print a customised ldap.conf to log for test purpose
    log.debug(LDAP_CONF_TEMPLATE.format(
        basedn=basedn,
        ldap_uri=inst.ldapuri,
        ldap_dns_uri=basedn_to_ldap_dns_uri(basedn),
    ))

DISPLAY_TEMPLATE = """
# This is a generic list of LDAP client configuration parameters you may require
# for connecting a client to this server. Some of them may or may not apply
# to your application, so consult your application documentation for further
# assistance.
#
# This program makes a number of assumptions about your data and configuration
# which may not be correct. Be sure to check these values for your situation.

; ldap uri
; This is the uri of the server you will connect to and authenticate to. It
; must be a valid subjectAltName in the presented TLS certificate. Note that this
; is not an exhaustive list of your LDAP servers, and other applications in your
; network like load balancers may affect this. This is just what we derive from
; your current connection.
ldap_uri = {ldap_uri}

; ldap dns discovery uri
; In some environments, you may have DNS SRV records such as
; "_ldap._tcp.<domain name>". If these are present in your dns server, you can
; use the following uri.
ldap_uri = {ldap_dns_uri}

; ca_cert
; To correctly use TLS, you require the valid CA cert that issued your LDAP TLS
; certificates. Sometimes a copy of this may be in your server instance as
ca_cert = /etc/dirsrv/slapd-<instance>/ca.crt
; However that's not guaranteed. You can show the certs from the LDAP server
; by sshing to the server and running:
certutil -L -d /etc/dirsrv/slapd-<instance>/
; If you can identify the CA certificate name, you can then view it with:
certutil -L -n <ca cert name> -a -d /etc/dirsrv/slapd-<instance>/
; This should be a pem file you can use in your application's CA.
; Some applications don't require a ca certificate parameter, and will use the
; ca certificate from /etc/openldap/ldap.conf. You should configure ldap.conf
; in these cases. See the 'client_config ldap.conf' command in dsidm.

; basedn
; The basedn is the root suffix where all searches will originate from for
; LDAP objects.
basedn = {basedn}

; schema_type
; LDAP servers have different ways to structure their objects and group
; relationships. Legacy servers will use rfc2307, where as modern servers will
; use rfc2307bis (requires MemberOf plugin to be enabled). This is the schema
; setting of your directory based on your running configuration (if we can
; detect it).
schema_type = {schema_type}

; user/account basedn
; Some applications may optionally use a user/account basedn to limit searches
; in the directory. This can be for performance or security reasons. Generally
; you shouldn't need this, preferring to use groups and filters for access
; control.
user_basedn = {user_basedn}

; user filter
; This is an ldap filter that will return only user objects. Additionally some
; applications will template into the filter (similar to sql statements) or they
; will generate the filter based on attributes. We list a number of possible
; filters you might use, but you should customise this for your application.
;
; If you are using rfc2307bis, you can use this filter to provide authorisation
; support by adding filters such as: (memberOf=<groupdn>)
user_filter = {user_filter}
user_filter = {user_id_filter}

; group basedn
; Some applications may optionnaly use a group basedn to limit searches in the
; directory. This can be for performance or security reasons. Generally you
; shouldn't need this, preferring to use groups and filters for access control.
group_basedn = {group_basedn}

; group filter
; This is an ldap filter that will return only group objects. Additionally
; some applications will template into the filter (similar to sql statements)
; or they will generate the filter base on attributes. We list a number of
; possible filters you might use, but you should customise this for your
; application.
group_filter = {group_filter}
group_filter = {group_id_filter}

; attribute mappings
; Due to the variety of schemas and attribute mappings in LDAP, there are
; different representations of attributes and values. This is a guess at
; the mappings that exist in your server, and what attributes you should
; configure and use.
unique id = {uuid_attr}
user rdn = {user_rdn}
user identifier = {user_rdn}
group rdn = {group_rdn}
group member attribute = {group_member}

"""


def display(inst, basedn, log, args):

    users = nsUserAccounts(inst, basedn)
    groups = Groups(inst, basedn)

    schema_type = "rfc2307"
    try:
        mo_plugin = MemberOfPlugin(inst)
        if mo_plugin.status():
            schema_type = "rfc2307bis"
    except:
        schema_type = "unknown - likely access denied to memberof plugin config"

    # Get required information
    print(DISPLAY_TEMPLATE.format(
        ldap_uri=inst.ldapuri,
        ldap_dns_uri=basedn_to_ldap_dns_uri(basedn),
        basedn=basedn,
        schema_type=schema_type,
        user_basedn=users._basedn,
        user_filter=users._get_objectclass_filter(),
        user_id_filter=users._get_selector_filter('<PARAM>'),
        group_basedn=groups._basedn,
        group_filter=groups._get_objectclass_filter(),
        group_id_filter=groups._get_selector_filter('<PARAM>'),
        uuid_attr='nsUniqueId',
        user_rdn=users._filterattrs[0],
        group_rdn=groups._filterattrs[0],
        group_member='member',
    ))

    # Print required information to log for test purpose
    log.debug(DISPLAY_TEMPLATE.format(
        ldap_uri=inst.ldapuri,
        ldap_dns_uri=basedn_to_ldap_dns_uri(basedn),
        basedn=basedn,
        schema_type=schema_type,
        user_basedn=users._basedn,
        user_filter=users._get_objectclass_filter(),
        user_id_filter=users._get_selector_filter('<PARAM>'),
        group_basedn=groups._basedn,
        group_filter=groups._get_objectclass_filter(),
        group_id_filter=groups._get_selector_filter('<PARAM>'),
        uuid_attr='nsUniqueId',
        user_rdn=users._filterattrs[0],
        group_rdn=groups._filterattrs[0],
        group_member='member',
    ))

def create_parser(subparsers):
    client_config_parser = subparsers.add_parser('client_config',
        help="Display and generate client example configs for this LDAP server")

    subcommands = client_config_parser.add_subparsers(help='action')

    sssd_conf_parser = subcommands.add_parser('sssd.conf',
        help="Generate a SSSD configuration for this LDAP server")
    # Allowed login groups for filter?
    sssd_conf_parser.add_argument('allowed_group', nargs='?', help="The name of the group allowed access to this system")
    sssd_conf_parser.set_defaults(func=sssd_conf)

    ldap_conf_parser = subcommands.add_parser('ldap.conf',
        help="Generate an OpenLDAP ldap.conf configuration for this LDAP server")
    ldap_conf_parser.set_defaults(func=ldap_conf)

    display_parser = subcommands.add_parser('display',
        help="Display generic application parameters for LDAP connection")
    display_parser.set_defaults(func=display)
