1# -*- coding: utf-8 -*-
2
3# Copyright: (c) 2016, Peter Sagerson <psagers@ignorare.net>
4# Copyright: (c) 2016, Jiri Tyr <jiri.tyr@gmail.com>
5# Copyright: (c) 2017-2018 Keller Fuchs (@KellerFuchs) <kellerfuchs@hashbang.sh>
6#
7# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
8
9from __future__ import absolute_import, division, print_function
10__metaclass__ = type
11
12import traceback
13from ansible.module_utils.common.text.converters import to_native
14
15try:
16    import ldap
17    import ldap.sasl
18
19    HAS_LDAP = True
20
21    SASCL_CLASS = {
22        'gssapi': ldap.sasl.gssapi,
23        'external': ldap.sasl.external,
24    }
25except ImportError:
26    HAS_LDAP = False
27
28
29def gen_specs(**specs):
30    specs.update({
31        'bind_dn': dict(),
32        'bind_pw': dict(default='', no_log=True),
33        'dn': dict(required=True),
34        'referrals_chasing': dict(type='str', default='anonymous', choices=['disabled', 'anonymous']),
35        'server_uri': dict(default='ldapi:///'),
36        'start_tls': dict(default=False, type='bool'),
37        'validate_certs': dict(default=True, type='bool'),
38        'sasl_class': dict(choices=['external', 'gssapi'], default='external', type='str'),
39    })
40
41    return specs
42
43
44class LdapGeneric(object):
45    def __init__(self, module):
46        # Shortcuts
47        self.module = module
48        self.bind_dn = self.module.params['bind_dn']
49        self.bind_pw = self.module.params['bind_pw']
50        self.dn = self.module.params['dn']
51        self.referrals_chasing = self.module.params['referrals_chasing']
52        self.server_uri = self.module.params['server_uri']
53        self.start_tls = self.module.params['start_tls']
54        self.verify_cert = self.module.params['validate_certs']
55        self.sasl_class = self.module.params['sasl_class']
56
57        # Establish connection
58        self.connection = self._connect_to_ldap()
59
60    def fail(self, msg, exn):
61        self.module.fail_json(
62            msg=msg,
63            details=to_native(exn),
64            exception=traceback.format_exc()
65        )
66
67    def _connect_to_ldap(self):
68        if not self.verify_cert:
69            ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
70
71        connection = ldap.initialize(self.server_uri)
72
73        if self.referrals_chasing == 'disabled':
74            # Switch off chasing of referrals (https://github.com/ansible-collections/community.general/issues/1067)
75            connection.set_option(ldap.OPT_REFERRALS, 0)
76
77        if self.start_tls:
78            try:
79                connection.start_tls_s()
80            except ldap.LDAPError as e:
81                self.fail("Cannot start TLS.", e)
82
83        try:
84            if self.bind_dn is not None:
85                connection.simple_bind_s(self.bind_dn, self.bind_pw)
86            else:
87                klass = SASCL_CLASS.get(self.sasl_class, ldap.sasl.external)
88                connection.sasl_interactive_bind_s('', klass())
89        except ldap.LDAPError as e:
90            self.fail("Cannot bind to the server.", e)
91
92        return connection
93