Blame pynslcd/cfg.py

Packit 6bd9ab
Packit 6bd9ab
# cfg.py - module for accessing configuration information
Packit 6bd9ab
#
Packit 6bd9ab
# Copyright (C) 2010-2017 Arthur de Jong
Packit 6bd9ab
#
Packit 6bd9ab
# This library is free software; you can redistribute it and/or
Packit 6bd9ab
# modify it under the terms of the GNU Lesser General Public
Packit 6bd9ab
# License as published by the Free Software Foundation; either
Packit 6bd9ab
# version 2.1 of the License, or (at your option) any later version.
Packit 6bd9ab
#
Packit 6bd9ab
# This library is distributed in the hope that it will be useful,
Packit 6bd9ab
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6bd9ab
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6bd9ab
# Lesser General Public License for more details.
Packit 6bd9ab
#
Packit 6bd9ab
# You should have received a copy of the GNU Lesser General Public
Packit 6bd9ab
# License along with this library; if not, write to the Free Software
Packit 6bd9ab
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit 6bd9ab
# 02110-1301 USA
Packit 6bd9ab
Packit 6bd9ab
import logging
Packit 6bd9ab
import re
Packit 6bd9ab
Packit 6bd9ab
import ldap
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
# the number of threads to start
Packit 6bd9ab
threads = 5
Packit 6bd9ab
# the user id nslcd should be run as
Packit 6bd9ab
uid = None
Packit 6bd9ab
# the group id nslcd should be run as
Packit 6bd9ab
gid = None
Packit 6bd9ab
# the configured loggers
Packit 6bd9ab
logs = []
Packit 6bd9ab
Packit 6bd9ab
# the LDAP server to use
Packit 6bd9ab
uri = None  # FIXME: support multiple servers and have a fail-over mechanism
Packit 6bd9ab
# LDAP protocol version to use (perhaps fix at 3?)
Packit 6bd9ab
ldap_version = ldap.VERSION3
Packit 6bd9ab
# the DN to use when binding
Packit 6bd9ab
binddn = None  # FIXME: add support
Packit 6bd9ab
bindpw = None  # FIXME: add support
Packit 6bd9ab
# the DN to use to perform password modifications as root
Packit 6bd9ab
rootpwmoddn = None
Packit 6bd9ab
rootpwmodpw = None
Packit 6bd9ab
Packit 6bd9ab
# SASL configuration
Packit 6bd9ab
sasl_mech = None  # FIXME: add support
Packit 6bd9ab
sasl_realm = None  # FIXME: add support
Packit 6bd9ab
sasl_authcid = None  # FIXME: add support
Packit 6bd9ab
sasl_authzid = None  # FIXME: add support
Packit 6bd9ab
sasl_secprops = None  # FIXME: add support
Packit 6bd9ab
sasl_canonicalize = None  # FIXME: add support
Packit 6bd9ab
Packit 6bd9ab
# LDAP bases to search
Packit 6bd9ab
bases = []
Packit 6bd9ab
# default search scope for searches
Packit 6bd9ab
scope = ldap.SCOPE_SUBTREE
Packit 6bd9ab
Packit 6bd9ab
deref = ldap.DEREF_NEVER
Packit 6bd9ab
referrals = True
Packit 6bd9ab
Packit 6bd9ab
# timing configuration
Packit 6bd9ab
bind_timelimit = 10  # FIXME: add support
Packit 6bd9ab
timelimit = ldap.NO_LIMIT
Packit 6bd9ab
idle_timelimit = 0  # FIXME: add support
Packit 6bd9ab
reconnect_sleeptime = 1  # FIXME: add support
Packit 6bd9ab
reconnect_retrytime = 10
Packit 6bd9ab
Packit 6bd9ab
# SSL/TLS options
Packit 6bd9ab
ssl = None
Packit 6bd9ab
tls_reqcert = None
Packit 6bd9ab
tls_cacertdir = None
Packit 6bd9ab
tls_cacertfile = None
Packit 6bd9ab
tls_randfile = None
Packit 6bd9ab
tls_ciphers = None
Packit 6bd9ab
tls_cert = None
Packit 6bd9ab
tls_key = None
Packit 6bd9ab
Packit 6bd9ab
# other options
Packit 6bd9ab
pagesize = 0  # FIXME: add support
Packit 6bd9ab
nss_initgroups_ignoreusers = set()
Packit 6bd9ab
nss_min_uid = 0
Packit 6bd9ab
nss_uid_offset = 0
Packit 6bd9ab
nss_gid_offset = 0
Packit 6bd9ab
nss_nested_groups = False
Packit 6bd9ab
nss_getgrent_skipmembers = False
Packit 6bd9ab
nss_disable_enumeration = False
Packit 6bd9ab
validnames = re.compile(r'^[a-z0-9._@$][a-z0-9._@$ \\~-]{0,98}[a-z0-9._@$~-]$', re.IGNORECASE)
Packit 6bd9ab
pam_authz_searches = []
Packit 6bd9ab
pam_password_prohibit_message = None
Packit 6bd9ab
reconnect_invalidate = set()
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
# allowed boolean values
Packit 6bd9ab
_boolean_options = {'on': True, 'yes': True, 'true': True, '1': True,
Packit 6bd9ab
                    'off': False, 'no': False, 'false': False, '0': False}
Packit 6bd9ab
Packit 6bd9ab
# allowed log levels (we log notice which is unsupported in Python to warning)
Packit 6bd9ab
_log_levels = {'crit': logging.CRITICAL, 'error': logging.ERROR,
Packit 6bd9ab
               'err': logging.ERROR, 'warning': logging.WARNING,
Packit 6bd9ab
               'notice': logging.WARNING, 'info': logging.INFO,
Packit 6bd9ab
               'debug': logging.DEBUG, 'none': logging.INFO}
Packit 6bd9ab
Packit 6bd9ab
# allowed values for scope option
Packit 6bd9ab
if not hasattr(ldap, 'SCOPE_CHILDREN') and ldap.VENDOR_VERSION >= 20400:
Packit 6bd9ab
    ldap.SCOPE_CHILDREN = 3   # OpenLDAP extension
Packit 6bd9ab
_scope_options = dict(sub=ldap.SCOPE_SUBTREE, subtree=ldap.SCOPE_SUBTREE,
Packit 6bd9ab
                      one=ldap.SCOPE_ONELEVEL, onelevel=ldap.SCOPE_ONELEVEL,
Packit 6bd9ab
                      base=ldap.SCOPE_BASE)
Packit 6bd9ab
if hasattr(ldap, 'SCOPE_CHILDREN'):
Packit 6bd9ab
    _scope_options['children'] = ldap.SCOPE_CHILDREN
Packit 6bd9ab
Packit 6bd9ab
# allowed values for the deref option
Packit 6bd9ab
_deref_options = dict(never=ldap.DEREF_NEVER,
Packit 6bd9ab
                      searching=ldap.DEREF_SEARCHING,
Packit 6bd9ab
                      finding=ldap.DEREF_FINDING,
Packit 6bd9ab
                      always=ldap.DEREF_ALWAYS)
Packit 6bd9ab
Packit 6bd9ab
# allowed values for the ssl option
Packit 6bd9ab
_ssl_options = dict(start_tls='STARTTLS', starttls='STARTTLS',
Packit 6bd9ab
                    on='LDAPS', off=None)
Packit 6bd9ab
Packit 6bd9ab
# allowed values for the tls_reqcert option
Packit 6bd9ab
_tls_reqcert_options = {'never': ldap.OPT_X_TLS_NEVER,
Packit 6bd9ab
                        'no': ldap.OPT_X_TLS_NEVER,
Packit 6bd9ab
                        'allow': ldap.OPT_X_TLS_ALLOW,
Packit 6bd9ab
                        'try': ldap.OPT_X_TLS_TRY,
Packit 6bd9ab
                        'demand': ldap.OPT_X_TLS_DEMAND,
Packit 6bd9ab
                        'yes': ldap.OPT_X_TLS_DEMAND,
Packit 6bd9ab
                        'hard': ldap.OPT_X_TLS_HARD}
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
def _get_maps():
Packit 6bd9ab
    # separate function as not to pollute the namespace and avoid import loops
Packit 6bd9ab
    import alias, ether, group, host, netgroup, network, passwd
Packit 6bd9ab
    import protocol, rpc, service, shadow
Packit 6bd9ab
    import sys
Packit 6bd9ab
    return dict(
Packit 6bd9ab
            alias=alias, aliases=alias,
Packit 6bd9ab
            ether=ether, ethers=ether,
Packit 6bd9ab
            group=group,
Packit 6bd9ab
            host=host, hosts=host,
Packit 6bd9ab
            netgroup=netgroup,
Packit 6bd9ab
            network=network, networks=network,
Packit 6bd9ab
            passwd=passwd,
Packit 6bd9ab
            protocol=protocol, protocols=protocol,
Packit 6bd9ab
            rpc=rpc,
Packit 6bd9ab
            service=service, services=service,
Packit 6bd9ab
            shadow=shadow,
Packit 6bd9ab
            none=sys.modules[__name__]
Packit 6bd9ab
        )
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
class ParseError(Exception):
Packit 6bd9ab
Packit 6bd9ab
    def __init__(self, filename, lineno, message):
Packit 6bd9ab
        self.message = '%s:%d: %s' % (filename, lineno, message)
Packit 6bd9ab
Packit 6bd9ab
    def __repr__(self):
Packit 6bd9ab
        return self.message
Packit 6bd9ab
Packit 6bd9ab
    __str__ = __repr__
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
def read(filename):
Packit 6bd9ab
    maps = _get_maps()
Packit 6bd9ab
    lineno = 0
Packit 6bd9ab
    for line in open(filename, 'r'):
Packit 6bd9ab
        lineno += 1
Packit 6bd9ab
        line = line.strip()
Packit 6bd9ab
        # skip comments and blank lines
Packit 6bd9ab
        if re.match('(#.*)?$', line, re.IGNORECASE):
Packit 6bd9ab
            continue
Packit 6bd9ab
        # parse options with a single integer argument
Packit 6bd9ab
        m = re.match('(?P<keyword>threads|ldap_version|bind_timelimit|timelimit|idle_timelimit|reconnect_sleeptime|reconnect_retrytime|pagesize|nss_min_uid|nss_uid_offset|nss_gid_offset)\s+(?P<value>\d+)',
Packit 6bd9ab
                     line, re.IGNORECASE)
Packit 6bd9ab
        if m:
Packit 6bd9ab
            globals()[m.group('keyword').lower()] = int(m.group('value'))
Packit 6bd9ab
            continue
Packit 6bd9ab
        # parse options with a single boolean argument
Packit 6bd9ab
        m = re.match('(?P<keyword>referrals|nss_nested_groups|nss_getgrent_skipmembers|nss_disable_enumeration)\s+(?P<value>%s)' %
Packit 6bd9ab
                         '|'.join(_boolean_options.keys()),
Packit 6bd9ab
                     line, re.IGNORECASE)
Packit 6bd9ab
        if m:
Packit 6bd9ab
            globals()[m.group('keyword').lower()] = _boolean_options[m.group('value').lower()]
Packit 6bd9ab
            continue
Packit 6bd9ab
        # parse options with a single no-space value
Packit 6bd9ab
        m = re.match('(?P<keyword>uid|gid|bindpw|rootpwmodpw|sasl_mech)\s+(?P<value>\S+)',
Packit 6bd9ab
                     line, re.IGNORECASE)
Packit 6bd9ab
        if m:
Packit 6bd9ab
            globals()[m.group('keyword').lower()] = m.group('value')
Packit 6bd9ab
            continue
Packit 6bd9ab
        # parse options with a single value that can contain spaces
Packit 6bd9ab
        m = re.match('(?P<keyword>binddn|rootpwmoddn|sasl_realm|sasl_authcid|sasl_authzid|sasl_secprops|krb5_ccname|tls_cacertdir|tls_cacertfile|tls_randfile|tls_ciphers|tls_cert|tls_key|pam_password_prohibit_message)\s+(?P<value>\S.*)',
Packit 6bd9ab
                     line, re.IGNORECASE)
Packit 6bd9ab
        if m:
Packit 6bd9ab
            globals()[m.group('keyword').lower()] = m.group('value')
Packit 6bd9ab
            continue
Packit 6bd9ab
        # log <SCHEME> [<LEVEL>]
Packit 6bd9ab
        m = re.match('log\s+(?P<scheme>syslog|/\S*)(\s+(?P<level>%s))?' %
Packit 6bd9ab
                         '|'.join(_log_levels.keys()),
Packit 6bd9ab
                     line, re.IGNORECASE)
Packit 6bd9ab
        if m:
Packit 6bd9ab
            logs.append((m.group('scheme'), _log_levels[str(m.group('level')).lower()]))
Packit 6bd9ab
            continue
Packit 6bd9ab
        # uri <URI>
Packit 6bd9ab
        m = re.match('uri\s+(?P<uri>\S+)', line, re.IGNORECASE)
Packit 6bd9ab
        if m:
Packit 6bd9ab
            # FIXME: support multiple URI values
Packit 6bd9ab
            # FIXME: support special DNS and DNS:domain values
Packit 6bd9ab
            global uri
Packit 6bd9ab
            uri = m.group('uri')
Packit 6bd9ab
            continue
Packit 6bd9ab
        # base <MAP>? <BASEDN>
Packit 6bd9ab
        m = re.match('base\s+((?P<map>%s)\s+)?(?P<value>\S.*)' %
Packit 6bd9ab
                         '|'.join(maps.keys()),
Packit 6bd9ab
                     line, re.IGNORECASE)
Packit 6bd9ab
        if m:
Packit 6bd9ab
            mod = maps[str(m.group('map')).lower()]
Packit 6bd9ab
            if not hasattr(mod, 'bases'):
Packit 6bd9ab
                mod.bases = []
Packit 6bd9ab
            mod.bases.append(m.group('value'))
Packit 6bd9ab
            continue
Packit 6bd9ab
        # filter <MAP> <SEARCHFILTER>
Packit 6bd9ab
        m = re.match('filter\s+(?P<map>%s)\s+(?P<value>\S.*)' %
Packit 6bd9ab
                         '|'.join(maps.keys()),
Packit 6bd9ab
                     line, re.IGNORECASE)
Packit 6bd9ab
        if m:
Packit 6bd9ab
            mod = maps[m.group('map').lower()]
Packit 6bd9ab
            mod.filter = m.group('value')
Packit 6bd9ab
            continue
Packit 6bd9ab
        # scope <MAP>? <SCOPE>
Packit 6bd9ab
        m = re.match('scope\s+((?P<map>%s)\s+)?(?P<value>%s)' % (
Packit 6bd9ab
                         '|'.join(maps.keys()),
Packit 6bd9ab
                         '|'.join(_scope_options.keys())),
Packit 6bd9ab
                     line, re.IGNORECASE)
Packit 6bd9ab
        if m:
Packit 6bd9ab
            mod = maps[str(m.group('map')).lower()]
Packit 6bd9ab
            mod.scope = _scope_options[m.group('value').lower()]
Packit 6bd9ab
            continue
Packit 6bd9ab
        # map <MAP> <ATTRIBUTE> <ATTMAPPING>
Packit 6bd9ab
        m = re.match('map\s+(?P<map>%s)\s+(?P<attribute>\S+)\s+(?P<value>\S.*)' %
Packit 6bd9ab
                         '|'.join(maps.keys()),
Packit 6bd9ab
                     line, re.IGNORECASE)
Packit 6bd9ab
        if m:
Packit 6bd9ab
            mod = maps[m.group('map').lower()]
Packit 6bd9ab
            attribute = m.group('attribute')
Packit 6bd9ab
            if attribute not in mod.attmap:
Packit 6bd9ab
                raise ParseError(filename, lineno, 'attribute %s unknown' % attribute)
Packit 6bd9ab
            mod.attmap[attribute] = m.group('value')
Packit 6bd9ab
            # TODO: filter out attributes that cannot be an expression
Packit 6bd9ab
            continue
Packit 6bd9ab
        # deref <DEREF>
Packit 6bd9ab
        m = re.match('deref\s+(?P<value>%s)' % '|'.join(_deref_options.keys()),
Packit 6bd9ab
                     line, re.IGNORECASE)
Packit 6bd9ab
        if m:
Packit 6bd9ab
            global deref
Packit 6bd9ab
            deref = _deref_options[m.group('value').lower()]
Packit 6bd9ab
            continue
Packit 6bd9ab
        # nss_initgroups_ignoreusers <USER,USER>|<ALLLOCAL>
Packit 6bd9ab
        m = re.match('nss_initgroups_ignoreusers\s+(?P<value>\S.*)',
Packit 6bd9ab
                     line, re.IGNORECASE)
Packit 6bd9ab
        if m:
Packit 6bd9ab
            users = m.group('value')
Packit 6bd9ab
            if users.lower() == 'alllocal':
Packit 6bd9ab
                # get all users known to the system currently (since nslcd
Packit 6bd9ab
                # isn't yet running, this should work)
Packit 6bd9ab
                import pwd
Packit 6bd9ab
                users = (x.pw_name for x in pwd.getpwall())
Packit 6bd9ab
            else:
Packit 6bd9ab
                users = users.split(',')
Packit 6bd9ab
                # TODO: warn about unknown users
Packit 6bd9ab
            nss_initgroups_ignoreusers.update(users)
Packit 6bd9ab
            continue
Packit 6bd9ab
        # pam_authz_search <FILTER>
Packit 6bd9ab
        m = re.match('pam_authz_search\s+(?P<value>\S.*)', line, re.IGNORECASE)
Packit 6bd9ab
        if m:
Packit 6bd9ab
            from expr import Expression
Packit 6bd9ab
            pam_authz_searches.append(Expression(m.group('value')))
Packit 6bd9ab
            # TODO: check pam_authz_search expression to only contain
Packit 6bd9ab
            # username, service, ruser, rhost, tty, hostname, fqdn, dn or
Packit 6bd9ab
            # uid variables
Packit 6bd9ab
            continue
Packit 6bd9ab
        # ssl <on|off|start_tls>
Packit 6bd9ab
        m = re.match('ssl\s+(?P<value>%s)' % '|'.join(_ssl_options.keys()),
Packit 6bd9ab
                     line, re.IGNORECASE)
Packit 6bd9ab
        if m:
Packit 6bd9ab
            global ssl
Packit 6bd9ab
            ssl = _ssl_options[m.group('value').lower()]
Packit 6bd9ab
            continue
Packit 6bd9ab
        # sasl_canonicalize yes|no
Packit 6bd9ab
        m = re.match('(ldap_?)?sasl_(?P<no>no)?canon(icali[sz]e)?\s+(?P<value>%s)' %
Packit 6bd9ab
                         '|'.join(_boolean_options.keys()),
Packit 6bd9ab
                     line, re.IGNORECASE)
Packit 6bd9ab
        if m:
Packit 6bd9ab
            global sasl_canonicalize
Packit 6bd9ab
            sasl_canonicalize = _boolean_options[m.group('value').lower()]
Packit 6bd9ab
            if m.group('no'):
Packit 6bd9ab
                sasl_canonicalize = not sasl_canonicalize
Packit 6bd9ab
            continue
Packit 6bd9ab
        # tls_reqcert <demand|hard|yes...>
Packit 6bd9ab
        m = re.match('tls_reqcert\s+(?P<value>%s)' %
Packit 6bd9ab
                         '|'.join(_tls_reqcert_options.keys()),
Packit 6bd9ab
                     line, re.IGNORECASE)
Packit 6bd9ab
        if m:
Packit 6bd9ab
            global tls_reqcert
Packit 6bd9ab
            tls_reqcert = _tls_reqcert_options[m.group('value').lower()]
Packit 6bd9ab
            continue
Packit 6bd9ab
        # validnames /REGEX/i?
Packit 6bd9ab
        m = re.match('validnames\s+/(?P<value>.*)/(?P<flags>[i]?)$',
Packit 6bd9ab
                     line, re.IGNORECASE)
Packit 6bd9ab
        if m:
Packit 6bd9ab
            global validnames
Packit 6bd9ab
            flags = 0 | re.IGNORECASE if m.group('flags') == 'i' else 0
Packit 6bd9ab
            validnames = re.compile(m.group('value'), flags=flags)
Packit 6bd9ab
            continue
Packit 6bd9ab
        # reconnect_invalidate <MAP>,<MAP>,...
Packit 6bd9ab
        m = re.match('reconnect_invalidate\s+(?P<value>\S.*)',
Packit 6bd9ab
                     line, re.IGNORECASE)
Packit 6bd9ab
        if m:
Packit 6bd9ab
            dbs = re.split('[ ,]+', m.group('value').lower())
Packit 6bd9ab
            for db in dbs:
Packit 6bd9ab
                if db not in maps.keys() + ['nfsidmap']:
Packit 6bd9ab
                    raise ParseError(filename, lineno, 'map %s unknown' % db)
Packit 6bd9ab
            reconnect_invalidate.update(dbs)
Packit 6bd9ab
            continue
Packit 6bd9ab
        # unrecognised line
Packit 6bd9ab
        raise ParseError(filename, lineno, 'error parsing line %r' % line)
Packit 6bd9ab
    # if logging is not configured, default to syslog
Packit 6bd9ab
    if not logs:
Packit 6bd9ab
        logs.append(('syslog', logging.INFO))
Packit 6bd9ab
    # dump config (debugging code)
Packit 6bd9ab
    for k, v in globals().items():
Packit 6bd9ab
        if not k.startswith('_'):
Packit 6bd9ab
            logging.debug('%s=%r', k, v)
Packit 6bd9ab
Packit 6bd9ab
Packit 6bd9ab
def get_usergid():
Packit 6bd9ab
    """Return user info and group id."""
Packit 6bd9ab
    import pwd
Packit 6bd9ab
    import grp
Packit 6bd9ab
    u = pwd.getpwnam(uid)
Packit 6bd9ab
    if gid is None:
Packit 6bd9ab
        g = u.pw_gid
Packit 6bd9ab
    else:
Packit 6bd9ab
        g = grp.getgrnam(gid).gr_gid
Packit 6bd9ab
    return u, g