Imported Debian patch 4.7.2-3

This commit is contained in:
Timo Aaltonen
2019-05-06 08:43:34 +03:00
committed by Mario Fetka
parent 27edeba051
commit 8bc559c5a1
917 changed files with 1068993 additions and 1184676 deletions

View File

@@ -1,7 +1,7 @@
# Makefile.in generated by automake 1.16.2 from Makefile.am.
# Makefile.in generated by automake 1.16.1 from Makefile.am.
# @configure_input@
# Copyright (C) 1994-2020 Free Software Foundation, Inc.
# Copyright (C) 1994-2018 Free Software Foundation, Inc.
# This Makefile.in is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -185,8 +185,6 @@ JSLINT = @JSLINT@
KRAD_LIBS = @KRAD_LIBS@
KRB5KDC_SERVICE = @KRB5KDC_SERVICE@
KRB5_CFLAGS = @KRB5_CFLAGS@
KRB5_GSSAPI_CFLAGS = @KRB5_GSSAPI_CFLAGS@
KRB5_GSSAPI_LIBS = @KRB5_GSSAPI_LIBS@
KRB5_LIBS = @KRB5_LIBS@
LD = @LD@
LDAP_CFLAGS = @LDAP_CFLAGS@
@@ -229,10 +227,11 @@ NM = @NM@
NMEDIT = @NMEDIT@
NSPR_CFLAGS = @NSPR_CFLAGS@
NSPR_LIBS = @NSPR_LIBS@
NSS_CFLAGS = @NSS_CFLAGS@
NSS_LIBS = @NSS_LIBS@
NUM_VERSION = @NUM_VERSION@
OBJDUMP = @OBJDUMP@
OBJEXT = @OBJEXT@
ODS_GROUP = @ODS_GROUP@
ODS_USER = @ODS_USER@
OTOOL = @OTOOL@
OTOOL64 = @OTOOL64@
@@ -253,6 +252,8 @@ POPT_LIBS = @POPT_LIBS@
POSUB = @POSUB@
PYLINT = @PYLINT@
PYTHON = @PYTHON@
PYTHON2 = @PYTHON2@
PYTHON3 = @PYTHON3@
PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
PYTHON_INSTALL_EXTRA_OPTIONS = @PYTHON_INSTALL_EXTRA_OPTIONS@
PYTHON_PLATFORM = @PYTHON_PLATFORM@
@@ -340,9 +341,7 @@ program_transform_name = @program_transform_name@
psdir = @psdir@
pyexecdir = @pyexecdir@
pythondir = @pythondir@
runstatedir = @runstatedir@
sbindir = @sbindir@
selinux_makefile = @selinux_makefile@
sharedstatedir = @sharedstatedir@
srcdir = @srcdir@
sysconfdir = @sysconfdir@

View File

@@ -722,7 +722,7 @@ Key Example value Description
============= ============================= =======================
bin '/usr/bin' Dir. containing script
dot_ipa '/home/jderose/.ipa' User config directory
home os.path.expanduser('~') User home dir.
home os.environ['HOME'] User home dir.
ipalib '.../site-packages/ipalib' Dir. of ipalib package
mode 'unit_test' The mode ipalib is in
script sys.argv[0] Path of script

View File

@@ -41,7 +41,7 @@ PERMISSIONS = ["read", "write", "add", "delete", "search", "compare",
"selfwrite", "proxy", "all"]
class ACI:
class ACI(object):
"""
Holds the basic data for an ACI entry, as stored in the cn=accounts
entry in LDAP. Has methods to parse an ACI string and export to an
@@ -83,7 +83,7 @@ class ACI:
op = v['operator']
if type(v['expression']) in (tuple, list):
target = ""
for l in self._unique_list(v['expression']):
for l in v['expression']:
target = target + l + " || "
target = target[:-4]
aci = aci + "(%s %s \"%s\")" % (t, op, target)
@@ -92,20 +92,6 @@ class ACI:
aci = aci + "(version 3.0;acl \"%s\";%s (%s) %s %s \"%s\"" % (self.name, self.action, ",".join(self.permissions), self.bindrule['keyword'], self.bindrule['operator'], self.bindrule['expression']) + ";)"
return aci
def _unique_list(self, l):
"""
A set() doesn't maintain order so make a list unique ourselves.
The number of entries in our lists are always going to be
relatively low and this code will be called infrequently
anyway so the overhead will be small.
"""
unique = []
for item in l:
if item not in unique:
unique.append(item)
return unique
def _remove_quotes(self, s):
# Remove leading and trailing quotes
if s.startswith('"'):
@@ -163,9 +149,7 @@ class ACI:
if not bindperms or len(bindperms.groups()) < 3:
raise SyntaxError("malformed ACI, permissions match failed %s" % acistr)
self.action = bindperms.group(1)
self.permissions = self._unique_list(
bindperms.group(2).replace(' ','').split(',')
)
self.permissions = bindperms.group(2).replace(' ','').split(',')
self.set_bindrule(bindperms.group(3))
def validate(self):
@@ -181,7 +165,7 @@ class ACI:
raise SyntaxError("invalid permission: '%s'" % p)
if not self.name:
raise SyntaxError("name must be set")
if not isinstance(self.name, str):
if not isinstance(self.name, six.string_types):
raise SyntaxError("name must be a string")
if not isinstance(self.target, dict) or len(self.target) == 0:
raise SyntaxError("target must be a non-empty dictionary")
@@ -191,11 +175,6 @@ class ACI:
raise SyntaxError("bindrule is missing a component")
return True
def set_permissions(self, permissions):
if type(permissions) not in (tuple, list):
permissions = [permissions]
self.permissions = self._unique_list(permissions)
def set_target_filter(self, filter, operator="="):
self.target['targetfilter'] = {}
if not filter.startswith("("):
@@ -211,7 +190,7 @@ class ACI:
if type(attr) not in (tuple, list):
attr = [attr]
self.target['targetattr'] = {}
self.target['targetattr']['expression'] = self._unique_list(attr)
self.target['targetattr']['expression'] = attr
self.target['targetattr']['operator'] = operator
def set_target(self, target, operator="="):

View File

@@ -23,11 +23,13 @@ Foundational classes and functions.
import re
import six
from ipalib.constants import NAME_REGEX, NAME_ERROR
from ipalib.constants import TYPE_ERROR, SET_ERROR, DEL_ERROR, OVERRIDE_ERROR
class ReadOnly:
class ReadOnly(object):
"""
Base class for classes that can be locked into a read-only state.
@@ -267,7 +269,7 @@ class NameSpace(ReadOnly):
through a dictionary interface. For example, say we create a `NameSpace`
instance from a list containing a single member, like this:
>>> class my_member:
>>> class my_member(object):
... name = 'my_name'
...
>>> namespace = NameSpace([my_member])
@@ -285,7 +287,7 @@ class NameSpace(ReadOnly):
For a more detailed example, say we create a `NameSpace` instance from a
generator like this:
>>> class Member:
>>> class Member(object):
... def __init__(self, i):
... self.i = i
... self.name = self.__name__ = 'member%d' % i
@@ -466,7 +468,7 @@ class NameSpace(ReadOnly):
:param key: The name or index of a member, or a slice object.
"""
key = getattr(key, '__name__', key)
if isinstance(key, str):
if isinstance(key, six.string_types):
return self.__map[key]
if type(key) in (int, slice):
return self.__members[key]

View File

@@ -23,7 +23,6 @@ Functionality for Command Line Interface.
from __future__ import print_function
import atexit
import builtins
import importlib
import logging
import textwrap
@@ -49,11 +48,14 @@ import six
from six.moves import input
from ipalib.util import (
check_client_configuration, get_pager, get_terminal_height, open_in_pager
check_client_configuration, get_terminal_height, open_in_pager
)
if six.PY3:
unicode = str
import builtins # pylint: disable=import-error
else:
import __builtin__ as builtins # pylint: disable=import-error
if six.PY2:
reload(sys) # pylint: disable=reload-builtin, undefined-variable
@@ -309,7 +311,7 @@ class textui(backend.Backend):
objectClass: top
objectClass: someClass
"""
assert isinstance(attr, str)
assert isinstance(attr, six.string_types)
if not isinstance(value, (list, tuple)):
# single-value attribute
self.print_indented(format % (attr, self.encode_binary(value)), indent)
@@ -448,7 +450,7 @@ class textui(backend.Backend):
------------------
Only dashed above.
"""
assert isinstance(dash, str)
assert isinstance(dash, six.string_types)
assert len(dash) == 1
dashes = dash * len(string)
if above:
@@ -702,7 +704,7 @@ class help(frontend.Local):
"""
Display help for a command or topic.
"""
class Writer:
class Writer(object):
"""
Writer abstraction
"""
@@ -721,11 +723,9 @@ class help(frontend.Local):
self.buffer.append(unicode(string))
def write(self):
pager = get_pager()
if pager and self.buffer_length > get_terminal_height():
if self.buffer_length > get_terminal_height():
data = "\n".join(self.buffer).encode("utf-8")
open_in_pager(data, pager)
open_in_pager(data)
else:
try:
for line in self.buffer:
@@ -949,14 +949,6 @@ class show_mappings(frontend.Command):
print(to_cli(item[0]).ljust(mcl)+' : '+item[1])
class IPACompleter(rlcompleter.Completer):
def _callable_postfix(self, val, word):
# Don't add '(' postfix for callable API objects
if isinstance(val, (plugable.APINameSpace, plugable.API)):
return word
return super()._callable_postfix(val, word)
class console(frontend.Command):
"""Start the IPA interactive Python console, or run a script.
@@ -972,7 +964,7 @@ class console(frontend.Command):
def _setup_tab_completion(self, local):
readline.parse_and_bind("tab: complete")
# completer with custom locals
readline.set_completer(IPACompleter(local).complete)
readline.set_completer(rlcompleter.Completer(local).complete)
# load history
history = os.path.join(api.env.dot_ipa, "console.history")
try:
@@ -1090,7 +1082,7 @@ cli_application_commands = (
)
class Collector:
class Collector(object):
def __init__(self):
object.__setattr__(self, '_Collector__options', {})
@@ -1462,14 +1454,14 @@ def run(api):
(_options, argv) = api.bootstrap_with_global_options(context='cli')
try:
check_client_configuration(env=api.env)
check_client_configuration()
except ScriptError as e:
sys.exit(e)
for klass in cli_plugins:
api.add_plugin(klass)
api.finalize()
if 'config_loaded' not in api.env and 'help' not in argv:
if not 'config_loaded' in api.env and not 'help' in argv:
raise NotConfiguredError()
sys.exit(api.Backend.cli.run(argv))
except KeyboardInterrupt:

View File

@@ -33,10 +33,12 @@ from __future__ import absolute_import
import os
from os import path
import sys
from urllib.parse import urlparse, urlunparse
from configparser import RawConfigParser, ParsingError
import six
# pylint: disable=import-error
from six.moves.urllib.parse import urlparse, urlunparse
from six.moves.configparser import RawConfigParser, ParsingError
# pylint: enable=import-error
from ipaplatform.tasks import tasks
from ipapython.dn import DN
@@ -44,15 +46,14 @@ from ipalib.base import check_name
from ipalib.constants import (
CONFIG_SECTION,
OVERRIDE_ERROR, SET_ERROR, DEL_ERROR,
TLS_VERSIONS, TLS_VERSION_DEFAULT_MIN, TLS_VERSION_DEFAULT_MAX,
TLS_VERSIONS
)
from ipalib import errors
if six.PY3:
unicode = str
class Env:
class Env(object):
"""
Store and retrieve environment variables.
@@ -251,7 +252,7 @@ class Env:
)
# pylint: enable=no-member
assert not hasattr(self, key)
if isinstance(value, str):
if isinstance(value, six.string_types):
value = value.strip()
if isinstance(value, bytes):
value = value.decode('utf-8')
@@ -450,18 +451,17 @@ class Env:
self.site_packages = path.dirname(self.ipalib)
self.script = path.abspath(sys.argv[0])
self.bin = path.dirname(self.script)
home = os.path.expanduser('~')
self.home = home if not home.startswith('~') else None
self.home = os.environ.get('HOME', None)
self.fips_mode = tasks.is_fips_enabled()
# Merge in overrides:
self._merge(**overrides)
# Determine if running in source tree. The root directory of
# IPA source directory contains ipasetup.py.in.
# Determine if running in source tree:
if 'in_tree' not in self:
self.in_tree = os.path.isfile(
os.path.join(self.site_packages, "ipasetup.py.in")
self.in_tree = (
self.bin == self.site_packages
and path.isfile(path.join(self.bin, 'setup.py'))
)
if self.in_tree and 'mode' not in self:
self.mode = 'developer'
@@ -561,8 +561,6 @@ class Env:
# Merge in context config file and then default config file:
mode = self.__d.get('mode') # pylint: disable=no-member
# documented public modes: production, developer
# internal modes: dummy, unit_test
if mode != 'dummy':
self._merge_from_file(self.conf)
self._merge_from_file(self.conf_default)
@@ -634,30 +632,20 @@ class Env:
# set the best known TLS version if min/max versions are not set
if 'tls_version_min' not in self:
self.tls_version_min = TLS_VERSION_DEFAULT_MIN
if (
self.tls_version_min is not None and
self.tls_version_min not in TLS_VERSIONS
):
self.tls_version_min = TLS_VERSIONS[-1]
elif self.tls_version_min not in TLS_VERSIONS:
raise errors.EnvironmentError(
"Unknown TLS version '{ver}' set in tls_version_min."
.format(ver=self.tls_version_min))
if 'tls_version_max' not in self:
self.tls_version_max = TLS_VERSION_DEFAULT_MAX
if (
self.tls_version_max is not None and
self.tls_version_max not in TLS_VERSIONS
):
self.tls_version_max = TLS_VERSIONS[-1]
elif self.tls_version_max not in TLS_VERSIONS:
raise errors.EnvironmentError(
"Unknown TLS version '{ver}' set in tls_version_max."
.format(ver=self.tls_version_max))
if (
self.tls_version_min is not None and
self.tls_version_max is not None and
self.tls_version_max < self.tls_version_min
):
if self.tls_version_max < self.tls_version_min:
raise errors.EnvironmentError(
"tls_version_min is set to a higher TLS version than "
"tls_version_max.")

View File

@@ -35,26 +35,6 @@ except Exception:
except Exception:
FQDN = None
# TLS related constants
# * SSL2 and SSL3 are broken.
# * TLS1.0 and TLS1.1 are no longer state of the art.
# * TLS1.2 and 1.3 are secure and working properly
# * Crypto policies restrict TLS range to 1.2 and 1.3. Python 3.6 cannot
# override the crypto policy.
TLS_VERSIONS = [
"ssl2",
"ssl3",
"tls1.0",
"tls1.1",
"tls1.2",
"tls1.3",
]
TLS_VERSION_MINIMAL = "tls1.2"
TLS_VERSION_MAXIMAL = "tls1.3"
TLS_VERSION_DEFAULT_MIN = None
TLS_VERSION_DEFAULT_MAX = None
# regular expression NameSpace member names must match:
NAME_REGEX = r'^[a-z][_a-z0-9]*[a-z0-9]$|^[a-z]$'
@@ -149,8 +129,6 @@ DEFAULT_CONFIG = (
('container_sysaccounts', DN(('cn', 'sysaccounts'), ('cn', 'etc'))),
('container_certmap', DN(('cn', 'certmap'))),
('container_certmaprules', DN(('cn', 'certmaprules'), ('cn', 'certmap'))),
('container_ca_renewal',
DN(('cn', 'ca_renewal'), ('cn', 'ipa'), ('cn', 'etc'))),
# Ports, hosts, and URIs:
# Following values do not have any reasonable default.
@@ -164,19 +142,15 @@ DEFAULT_CONFIG = (
('rpc_protocol', 'jsonrpc'),
# Define an inclusive range of SSL/TLS version support
('tls_version_min', TLS_VERSION_DEFAULT_MIN),
('tls_version_max', TLS_VERSION_DEFAULT_MAX),
('tls_version_min', 'tls1.0'),
('tls_version_max', 'tls1.2'),
# Time to wait for a service to start, in seconds.
# Note that systemd has a DefaultTimeoutStartSec of 90 seconds. Higher
# values are not effective unless systemd is reconfigured, too.
('startup_timeout', 120),
# Time to wait for a service to start, in seconds
('startup_timeout', 300),
# How long http connection should wait for reply [seconds].
('http_timeout', 30),
# How long to wait for an entry to appear on a replica
('replication_wait_timeout', 300),
# How long to wait for a certmonger request to finish
('certmonger_wait_timeout', 300),
# Web Application mount points
('mount_ipa', '/ipa/'),
@@ -251,7 +225,7 @@ DEFAULT_CONFIG = (
('site_packages', object), # The directory contaning ipalib
('script', object), # sys.argv[0]
('bin', object), # The directory containing the script
('home', object), # os.path.expanduser('~')
('home', object), # $HOME
# Vars set in Env._bootstrap():
('in_tree', object), # Whether or not running in-tree (bool)
@@ -310,17 +284,9 @@ IPA_CA_RECORD = "ipa-ca"
IPA_CA_NICKNAME = 'caSigningCert cert-pki-ca'
RENEWAL_CA_NAME = 'dogtag-ipa-ca-renew-agent'
RENEWAL_REUSE_CA_NAME = 'dogtag-ipa-ca-renew-agent-reuse'
RENEWAL_SELFSIGNED_CA_NAME = 'dogtag-ipa-ca-renew-agent-selfsigned'
# The RA agent cert is used for client cert authentication. In the past IPA
# used caServerCert profile, which adds clientAuth and serverAuth EKU. The
# serverAuth EKU caused trouble with NamedConstraints, see RHBZ#1670239.
RA_AGENT_PROFILE = 'caSubsystemCert'
# How long dbus clients should wait for CA certificate RPCs [seconds]
CA_DBUS_TIMEOUT = 120
# Maximum hostname length in Linux
MAXHOSTNAMELEN = 64
# regexp definitions
PATTERN_GROUPUSER_NAME = (
'(?!^[0-9]+$)^[a-zA-Z0-9_.][a-zA-Z0-9_.-]*[a-zA-Z0-9_.$-]?$'
@@ -333,10 +299,27 @@ ANON_USER = 'WELLKNOWN/ANONYMOUS'
IPAAPI_USER = 'ipaapi'
IPAAPI_GROUP = 'ipaapi'
# TLS related constants
TLS_VERSIONS = [
"ssl2",
"ssl3",
"tls1.0",
"tls1.1",
"tls1.2"
]
TLS_VERSION_MINIMAL = "tls1.0"
# Use cache path
USER_CACHE_PATH = (
os.environ.get('XDG_CACHE_HOME') or
os.path.expanduser('~/.cache')
os.path.join(
os.environ.get(
'HOME',
os.path.expanduser('~')
),
'.cache'
)
)
SOFTHSM_DNSSEC_TOKEN_LABEL = u'ipaDNSSEC'

View File

@@ -55,7 +55,7 @@ def get_extra_rrtype(name):
def has_cli_options(cmd, options, no_option_msg, allow_empty_attrs=False):
sufficient = ('setattr', 'addattr', 'delattr', 'rename', 'dnsttl')
sufficient = ('setattr', 'addattr', 'delattr', 'rename')
if any(k in options for k in sufficient):
return

View File

@@ -1462,15 +1462,6 @@ class CSRTemplateError(ExecutionError):
format = _('%(reason)s')
class AlreadyContainsValueError(ExecutionError):
"""
**4038** Raised when BaseLDAPAddAttribute operation fails because one
or more new values are already present.
"""
errno = 4038
format = _("'%(attr)s' already contains one or more values")
class BuiltinError(ExecutionError):
"""
**4100** Base class for builtin execution errors (*4100 - 4199*).
@@ -2016,7 +2007,5 @@ class GenericError(PublicError):
public_errors = tuple(sorted(
messages.iter_messages(globals(), PublicError), key=lambda E: E.errno))
errors_by_code = dict((e.errno, e) for e in public_errors)
if __name__ == '__main__':
messages.print_report('public errors', public_errors)

View File

@@ -1,73 +0,0 @@
#
# Copyright (C) 2020 FreeIPA Contributors see COPYING for license
#
"""
Facts about the installation
"""
import logging
import os
from . import sysrestore
from ipaplatform.paths import paths
logger = logging.getLogger(__name__)
# Used to determine install status
IPA_MODULES = [
'httpd', 'kadmin', 'dirsrv', 'pki-tomcatd', 'install', 'krb5kdc', 'named']
def is_ipa_configured():
"""
Use the state to determine if IPA has been configured.
"""
sstore = sysrestore.StateFile(paths.SYSRESTORE)
if sstore.has_state('installation'):
return sstore.get_state('installation', 'complete')
# Fall back to older method in case this is an existing installation
installed = False
fstore = sysrestore.FileStore(paths.SYSRESTORE)
for module in IPA_MODULES:
if sstore.has_state(module):
logger.debug('%s is configured', module)
installed = True
else:
logger.debug('%s is not configured', module)
if fstore.has_files():
logger.debug('filestore has files')
installed = True
else:
logger.debug('filestore is tracking no files')
return installed
def is_ipa_client_configured(on_master=False):
"""
Consider IPA client not installed if nothing is backed up
and default.conf file does not exist. If on_master is set to True,
the existence of default.conf file is not taken into consideration,
since it has been already created by ipa-server-install.
"""
fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
statestore = sysrestore.StateFile(paths.IPA_CLIENT_SYSRESTORE)
installed = statestore.get_state('installation', 'complete')
if installed is not None:
return installed
# Fall back to the old detection
installed = (
fstore.has_files() or (
not on_master and os.path.exists(paths.IPA_DEFAULT_CONF)
)
)
return installed

View File

@@ -29,7 +29,6 @@ from ipapython.ipautil import APIVersion
from ipalib.base import NameSpace
from ipalib.plugable import Plugin, APINameSpace
from ipalib.parameters import create_param, Param, Str, Flag
from ipalib.parameters import create_signature
from ipalib.parameters import Password # pylint: disable=unused-import
from ipalib.output import Output, Entry, ListOfEntries
from ipalib.text import _
@@ -38,7 +37,7 @@ from ipalib.errors import (ZeroArgumentError, MaxArgumentError, OverlapError,
ValidationError, ConversionError)
from ipalib import errors, messages
from ipalib.request import context, context_frame
from ipalib.util import classproperty, classobjectproperty, json_serialize
from ipalib.util import classproperty, json_serialize
if six.PY3:
unicode = str
@@ -434,26 +433,6 @@ class Command(HasParam):
topic = classproperty(__topic_getter)
@classobjectproperty
@classmethod
def __signature__(cls, obj):
# signature is cached on the class object
if hasattr(cls, "_signature"):
return cls._signature
# can only create signature for 'final' classes
# help(api.Command.user_show) breaks because pydoc inspects parent
# classes and baseuser plugin is not a registered object.
if cls.__subclasses__():
cls._signature = None
return None
# special, rare case: user calls help() on a plugin class instead of
# an instance
if obj is None:
from ipalib import api
obj = cls(api=api)
cls._signature = signature = create_signature(obj)
return signature
@property
def forwarded_name(self):
return self.full_name
@@ -596,7 +575,7 @@ class Command(HasParam):
if self.params[name].attribute and name in kw:
value = kw[name]
if isinstance(value, tuple):
yield (name, list(value))
yield (name, [v for v in value])
else:
yield (name, kw[name])
@@ -1090,14 +1069,15 @@ class Command(HasParam):
if o == 'value':
continue
if o.lower() == 'count' and result == 0:
elif o.lower() == 'count' and result == 0:
rv = 1
elif o.lower() == 'failed':
if entry_count(result) == 0:
# Don't display an empty failed list
continue
# Return an error to the shell
rv = 1
else:
# Return an error to the shell
rv = 1
if isinstance(outp, ListOfEntries):
textui.print_entries(result, order, labels, flags, print_all)
elif isinstance(result, (tuple, list)):
@@ -1319,7 +1299,7 @@ class Object(HasParam):
)
if self.primary_key:
json_dict['primary_key'] = self.primary_key.name
json_dict['methods'] = list(self.methods)
json_dict['methods'] = [m for m in self.methods]
return json_dict

View File

@@ -46,39 +46,8 @@ DBUS_CM_REQUEST_IF = 'org.fedorahosted.certmonger.request'
DBUS_CM_CA_IF = 'org.fedorahosted.certmonger.ca'
DBUS_PROPERTY_IF = 'org.freedesktop.DBus.Properties'
# These properties, if encountered in search criteria, result in a
# subset test instead of equality test.
ARRAY_PROPERTIES = ['template-hostname']
"""
Certmonger helper routines.
Search criteria
---------------
Functions that look up requests take a ``dict`` of search criteria.
In general, the key is a name of a property in the request property
interface. But there are some special cases with different
behaviour:
``nickname``
a.k.a. "request ID". If given, only the specified request is
retrieved (if it exists), and it is still tested against other
criteria.
``ca-name``
Test equality against the nickname of the CA (a.k.a. request
helper) object for the request.
``template-hostname``
Must be an iterable of DNS names. Tests that the given values
are a subset of the values defined on the Certmonger request.
"""
class _cm_dbus_object:
class _cm_dbus_object(object):
"""
Auxiliary class for convenient DBus object handling.
"""
@@ -187,12 +156,9 @@ class _certmonger(_cm_dbus_object):
DBUS_CM_IF)
def _get_requests(criteria):
def _get_requests(criteria=dict()):
"""
Get all requests that matches the provided criteria.
:param criteria: dict of criteria; see module doc for details
"""
if not isinstance(criteria, dict):
raise TypeError('"criteria" must be dict.')
@@ -213,23 +179,13 @@ def _get_requests(criteria):
for criterion in criteria:
if criterion == 'ca-name':
ca_path = request.obj_if.get_ca()
if ca_path is None:
raise RuntimeError("certmonger CA '%s' is not defined" %
criteria.get('ca-name'))
ca = _cm_dbus_object(cm.bus, cm, ca_path, DBUS_CM_CA_IF,
DBUS_CM_IF)
if criteria[criterion] != ca.obj_if.get_nickname():
break
elif criterion in ARRAY_PROPERTIES:
# perform subset test
expect = set(criteria[criterion])
got = request.prop_if.Get(DBUS_CM_REQUEST_IF, criterion)
if not expect.issubset(got):
break
value = ca.obj_if.get_nickname()
else:
value = request.prop_if.Get(DBUS_CM_REQUEST_IF, criterion)
if criteria[criterion] != value:
break
if value != criteria[criterion]:
break
else:
requests.append(request)
@@ -238,11 +194,11 @@ def _get_requests(criteria):
def _get_request(criteria):
"""
Find request that matches criteria. Return ``None`` if no match.
Raise ``RuntimeError`` if there is more than one matching request.
:param criteria: dict of criteria; see module doc for details
Find request that matches criteria.
If 'nickname' is specified other criteria are ignored because 'nickname'
uniquely identify single request.
When multiple or none request matches specified criteria RuntimeError is
raised.
"""
requests = _get_requests(criteria)
if len(requests) == 0:
@@ -280,11 +236,11 @@ def get_request_id(criteria):
If you don't know the certmonger request_id then try to find it by looking
through all the requests.
Return ``None`` if no match. Raise ``RuntimeError`` if there is
more than one matching request.
:param criteria: dict of criteria; see module doc for details
criteria is a tuple of key/value to search for. The more specific
the better. An error is raised if multiple request_ids are returned for
the same criteria.
None is returned if none of the criteria match.
"""
try:
request = _get_request(criteria)
@@ -350,7 +306,7 @@ def request_and_wait_for_cert(
certpath, subject, principal, nickname=None, passwd_fname=None,
dns=None, ca='IPA', profile=None,
pre_command=None, post_command=None, storage='NSSDB', perms=None,
resubmit_timeout=0, stop_tracking_on_error=False):
resubmit_timeout=0):
"""Request certificate, wait and possibly resubmit failing requests
Submit a cert request to certmonger and wait until the request has
@@ -370,11 +326,7 @@ def request_and_wait_for_cert(
deadline = time.time() + resubmit_timeout
while True: # until success, timeout, or error
try:
state = wait_for_request(req_id, api.env.http_timeout)
except RuntimeError as e:
logger.debug("wait_for_request raised %s", e)
state = 'TIMEOUT'
state = wait_for_request(req_id, api.env.replication_wait_timeout)
ca_error = get_request_value(req_id, 'ca-error')
if state == 'MONITORING' and ca_error is None:
# we got a winner, exiting
@@ -384,28 +336,22 @@ def request_and_wait_for_cert(
logger.debug(
"Cert request %s failed: %s (%s)", req_id, state, ca_error
)
if state in {'CA_REJECTED', 'CA_UNREACHABLE'}:
if state not in {'CA_REJECTED', 'CA_UNREACHABLE'}:
# probably unrecoverable error
logger.debug("Giving up on cert request %s", req_id)
break
if not resubmit_timeout:
elif not resubmit_timeout:
# no resubmit
break
if time.time() > deadline:
logger.debug("Request %s reached resubmit deadline", req_id)
elif time.time() > deadline:
logger.debug("Request %s reached resubmit dead line", req_id)
break
if state == 'TIMEOUT':
logger.debug("%s not in final state, continue waiting", req_id)
time.sleep(10)
else:
# sleep and resubmit
logger.debug("Sleep and resubmit cert request %s", req_id)
time.sleep(10)
resubmit_request(req_id)
if stop_tracking_on_error:
stop_tracking(request_id=req_id)
raise RuntimeError(
"Certificate issuance failed ({}: {})".format(state, ca_error)
)
@@ -481,8 +427,7 @@ def request_cert(
def start_tracking(
certpath, ca='IPA', nickname=None, pin=None, pinfile=None,
pre_command=None, post_command=None, profile=None, storage="NSSDB",
token_name=None, dns=None):
pre_command=None, post_command=None, profile=None, storage="NSSDB"):
"""
Tell certmonger to track the given certificate in either a file or an NSS
database. The certificate access can be protected by a password_file.
@@ -515,10 +460,6 @@ def start_tracking(
NSS or OpenSSL backend to track the certificate in ``certpath``
:param profile:
Which certificate profile should be used.
:param token_name:
Hardware token name for HSM support
:param dns:
List of DNS names
:returns: certificate tracking nickname.
"""
if storage == 'FILE':
@@ -559,12 +500,6 @@ def start_tracking(
params['cert-postsave-command'] = post_command
if profile:
params['ca-profile'] = profile
if token_name not in {None, "internal"}:
# only pass token names for external tokens (e.g. HSM)
params['key-token'] = token_name
params['cert-token'] = token_name
if dns is not None and len(dns) > 0:
params['DNS'] = dns
result = cm.obj_if.add_request(params)
try:
@@ -655,18 +590,6 @@ def resubmit_request(
update['template-is-ca'] = True
update['template-ca-path-length'] = -1 # no path length
# TODO: certmonger assumes some hard-coded defaults like RSA 2048.
# Try to fetch values from current cert rather.
for key, convert in [('key-size', int), ('key-type', str)]:
try:
value = request.prop_if.Get(DBUS_CM_REQUEST_IF, key)
except dbus.DBusException:
continue
else:
if value:
# convert dbus.Int64() to int, dbus.String() to str
update[key] = convert(value)
if len(update) > 0:
request.obj_if.modify(update)
request.obj_if.resubmit()
@@ -740,7 +663,7 @@ def modify_ca_helper(ca_name, helper):
return old_helper
def get_pin(token="internal"):
def get_pin(token):
"""
Dogtag stores its NSS pin in a file formatted as token:PIN.

View File

@@ -1,155 +0,0 @@
#
# Copyright (C) 2020 FreeIPA Contributors see COPYING for license
#
"""DNS forwarder and systemd-resolve1 helpers
"""
import ipaddress
import logging
import os
import socket
import dbus
from ipaplatform.paths import paths
from ipapython.dnsutil import get_ipa_resolver
logger = logging.getLogger(__name__)
_SYSTEMD_RESOLV_CONF = {
"/run/systemd/resolve/stub-resolv.conf",
"/run/systemd/resolve/resolv.conf",
"/lib/systemd/resolv.conf",
"/usr/lib/systemd/resolv.conf",
}
_DBUS_RESOLVE1_NAME = "org.freedesktop.resolve1"
_DBUS_RESOLVE1_PATH = "/org/freedesktop/resolve1"
_DBUS_RESOLVE1_MANAGER_IF = "org.freedesktop.resolve1.Manager"
_DBUS_PROPERTY_IF = "org.freedesktop.DBus.Properties"
# netlink interface index for resolve1 global settings and loopback
IFINDEX_GLOBAL = 0
IFINDEX_LOOPBACK = 1
def detect_resolve1_resolv_conf():
"""Detect if /etc/resolv.conf is managed by systemd-resolved
See man(5) NetworkManager.conf
"""
try:
dest = os.readlink(paths.RESOLV_CONF)
except OSError:
# not a link
return False
# convert path relative to /etc/resolv.conf to abs path
dest = os.path.normpath(
os.path.join(os.path.dirname(paths.RESOLV_CONF), dest)
)
return dest in _SYSTEMD_RESOLV_CONF
def get_resolve1_nameservers(*, with_ifindex=False):
"""Get list of DNS nameservers from systemd-resolved
:return: list of tuples (ifindex, ipaddress_obj)
"""
bus = dbus.SystemBus()
try:
resolve1 = bus.get_object(_DBUS_RESOLVE1_NAME, _DBUS_RESOLVE1_PATH)
prop_if = dbus.Interface(resolve1, _DBUS_PROPERTY_IF)
dns_prop = prop_if.Get(_DBUS_RESOLVE1_MANAGER_IF, "DNSEx")
finally:
bus.close()
results = []
for ifindex, af, dns_arr, port, sniname in dns_prop:
if port not in {0, 53} or sniname:
# non-default port, non-standard port, or SNI name configuration
# for DNS over TLS, e.g. 1.2.3.4:9953#example.com
continue
# convert packed format to IPAddress object (like inet_ntop)
if af == socket.AF_INET:
dnsip = ipaddress.IPv4Address(bytes(dns_arr))
elif af == socket.AF_INET6:
dnsip = ipaddress.IPv6Address(bytes(dns_arr))
else:
# neither IPv4 nor IPv6
continue
if with_ifindex:
# netlink interface index, see socket.if_nameindex()
ifindex = int(ifindex)
results.append((ifindex, dnsip))
else:
results.append(dnsip)
return results
def get_dnspython_nameservers(*, with_ifindex=False):
"""Get list of DNS nameservers from dnspython
On Linux dnspython parses /etc/resolv.conf for us
:return: list of tuples (ifindex, ipaddress_obj)
"""
results = []
for nameserver in get_ipa_resolver().nameservers:
nameserver = ipaddress.ip_address(nameserver)
if with_ifindex:
results.append((IFINDEX_GLOBAL, nameserver))
else:
results.append(nameserver)
return results
def get_nameservers():
"""Get list of unique, non-loopback DNS nameservers
:return: list of strings
"""
if detect_resolve1_resolv_conf():
logger.debug(
"systemd-resolved detected, fetching nameservers from D-Bus"
)
nameservers = get_resolve1_nameservers(with_ifindex=True)
else:
logger.debug(
"systemd-resolved not detected, parsing %s", paths.RESOLV_CONF
)
nameservers = get_dnspython_nameservers(with_ifindex=True)
logger.debug("Detected nameservers: %r", nameservers)
result = []
seen = set()
for ifindex, ip in nameservers:
# unique entries
if ip in seen:
continue
seen.add(ip)
# skip loopback
if ifindex == IFINDEX_LOOPBACK or ip.is_loopback:
continue
result.append(str(ip))
logger.debug("Use nameservers %r", result)
return result
if __name__ == "__main__":
from pprint import pprint
print("systemd-resolved detected:", detect_resolve1_resolv_conf())
print("Interfaces:", socket.if_nameindex())
print("dnspython nameservers:")
pprint(get_dnspython_nameservers(with_ifindex=True))
print("resolve1 nameservers:")
try:
pprint(get_resolve1_nameservers(with_ifindex=True))
except Exception as e:
print(e)
print("nameservers:", get_nameservers())

View File

@@ -1,21 +1,457 @@
# Authors: Mark McLoughlin <markmc@redhat.com>
#
# Copyright (C) 2020 FreeIPA Contributors see COPYING for license
# Copyright (C) 2007 Red Hat
# see file 'COPYING' for use and warranty information
#
# 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 3 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, see <http://www.gnu.org/licenses/>.
#
"""
Facade for ipalib.sysrestore for backwards compatibility
"""
#
# This module provides a very simple API which allows
# ipa-xxx-install --uninstall to restore certain
# parts of the system configuration to the way it was
# before ipa-server-install was first run
from ipalib import sysrestore as real_sysrestore
from __future__ import absolute_import
import logging
import os
import os.path
import shutil
import random
from hashlib import sha256
import six
# pylint: disable=import-error
if six.PY3:
# The SafeConfigParser class has been renamed to ConfigParser in Py3
from configparser import ConfigParser as SafeConfigParser
else:
from ConfigParser import SafeConfigParser
# pylint: enable=import-error
from ipaplatform.tasks import tasks
from ipaplatform.paths import paths
if six.PY3:
unicode = str
logger = logging.getLogger(__name__)
SYSRESTORE_PATH = paths.TMP
SYSRESTORE_INDEXFILE = "sysrestore.index"
SYSRESTORE_STATEFILE = "sysrestore.state"
class FileStore(real_sysrestore.FileStore):
def __init__(self, path=real_sysrestore.SYSRESTORE_PATH,
index_file=real_sysrestore.SYSRESTORE_INDEXFILE):
super(FileStore, self).__init__(path, index_file)
class FileStore(object):
"""Class for handling backup and restore of files"""
def __init__(self, path = SYSRESTORE_PATH, index_file = SYSRESTORE_INDEXFILE):
"""Create a _StoreFiles object, that uses @path as the
base directory.
The file @path/sysrestore.index is used to store information
about the original location of the saved files.
"""
self._path = path
self._index = os.path.join(self._path, index_file)
self.random = random.Random()
self.files = {}
self._load()
def _load(self):
"""Load the file list from the index file. @files will
be an empty dictionary if the file doesn't exist.
"""
logger.debug("Loading Index file from '%s'", self._index)
self.files = {}
p = SafeConfigParser()
p.optionxform = str
p.read(self._index)
for section in p.sections():
if section == "files":
for (key, value) in p.items(section):
self.files[key] = value
class StateFile(real_sysrestore.StateFile):
def __init__(self, path=real_sysrestore.SYSRESTORE_PATH,
state_file=real_sysrestore.SYSRESTORE_STATEFILE):
super(StateFile, self).__init__(path, state_file)
def save(self):
"""Save the file list to @_index. If @files is an empty
dict, then @_index should be removed.
"""
logger.debug("Saving Index File to '%s'", self._index)
if len(self.files) == 0:
logger.debug(" -> no files, removing file")
if os.path.exists(self._index):
os.remove(self._index)
return
p = SafeConfigParser()
p.optionxform = str
p.add_section('files')
for (key, value) in self.files.items():
p.set('files', key, str(value))
with open(self._index, "w") as f:
p.write(f)
def backup_file(self, path):
"""Create a copy of the file at @path - as long as an exact copy
does not already exist - which will be restored to its
original location by restore_files().
"""
logger.debug("Backing up system configuration file '%s'", path)
if not os.path.isabs(path):
raise ValueError("Absolute path required")
if not os.path.isfile(path):
logger.debug(" -> Not backing up - '%s' doesn't exist", path)
return
_reldir, backupfile = os.path.split(path)
with open(path, 'rb') as f:
cont_hash = sha256(f.read()).hexdigest()
filename = "{hexhash}-{bcppath}".format(
hexhash=cont_hash, bcppath=backupfile)
backup_path = os.path.join(self._path, filename)
if os.path.exists(backup_path):
logger.debug(" -> Not backing up - already have a copy of '%s'",
path)
return
shutil.copy2(path, backup_path)
stat = os.stat(path)
template = '{stat.st_mode},{stat.st_uid},{stat.st_gid},{path}'
self.files[filename] = template.format(stat=stat, path=path)
self.save()
def has_file(self, path):
"""Checks whether file at @path was added to the file store
Returns #True if the file exists in the file store, #False otherwise
"""
result = False
for _key, value in self.files.items():
_mode, _uid, _gid, filepath = value.split(',', 3)
if (filepath == path):
result = True
break
return result
def restore_file(self, path, new_path = None):
"""Restore the copy of a file at @path to its original
location and delete the copy.
Takes optional parameter @new_path which specifies the
location where the file is to be restored.
Returns #True if the file was restored, #False if there
was no backup file to restore
"""
if new_path is None:
logger.debug("Restoring system configuration file '%s'",
path)
else:
logger.debug("Restoring system configuration file '%s' to '%s'",
path, new_path)
if not os.path.isabs(path):
raise ValueError("Absolute path required")
if new_path is not None and not os.path.isabs(new_path):
raise ValueError("Absolute new path required")
mode = None
uid = None
gid = None
filename = None
for (key, value) in self.files.items():
(mode,uid,gid,filepath) = value.split(',', 3)
if (filepath == path):
filename = key
break
if not filename:
raise ValueError("No such file name in the index")
backup_path = os.path.join(self._path, filename)
if not os.path.exists(backup_path):
logger.debug(" -> Not restoring - '%s' doesn't exist",
backup_path)
return False
if new_path is not None:
path = new_path
shutil.copy(backup_path, path) # SELinux needs copy
os.remove(backup_path)
os.chown(path, int(uid), int(gid))
os.chmod(path, int(mode))
tasks.restore_context(path)
del self.files[filename]
self.save()
return True
def restore_all_files(self):
"""Restore the files in the inbdex to their original
location and delete the copy.
Returns #True if the file was restored, #False if there
was no backup file to restore
"""
if len(self.files) == 0:
return False
for (filename, value) in self.files.items():
(mode,uid,gid,path) = value.split(',', 3)
backup_path = os.path.join(self._path, filename)
if not os.path.exists(backup_path):
logger.debug(" -> Not restoring - '%s' doesn't exist",
backup_path)
continue
shutil.copy(backup_path, path) # SELinux needs copy
os.remove(backup_path)
os.chown(path, int(uid), int(gid))
os.chmod(path, int(mode))
tasks.restore_context(path)
# force file to be deleted
self.files = {}
self.save()
return True
def has_files(self):
"""Return True or False if there are any files in the index
Can be used to determine if a program is configured.
"""
return len(self.files) > 0
def untrack_file(self, path):
"""Remove file at path @path from list of backed up files.
Does not remove any files from the filesystem.
Returns #True if the file was untracked, #False if there
was no backup file to restore
"""
logger.debug("Untracking system configuration file '%s'", path)
if not os.path.isabs(path):
raise ValueError("Absolute path required")
filename = None
for (key, value) in self.files.items():
_mode, _uid, _gid, filepath = value.split(',', 3)
if (filepath == path):
filename = key
break
if not filename:
raise ValueError("No such file name in the index")
backup_path = os.path.join(self._path, filename)
if not os.path.exists(backup_path):
logger.debug(" -> Not restoring - '%s' doesn't exist",
backup_path)
return False
try:
os.unlink(backup_path)
except Exception as e:
logger.error('Error removing %s: %s', backup_path, str(e))
del self.files[filename]
self.save()
return True
class StateFile(object):
"""A metadata file for recording system state which can
be backed up and later restored.
StateFile gets reloaded every time to prevent loss of information
recorded by child processes. But we do not solve concurrency
because there is no need for it right now.
The format is something like:
[httpd]
running=True
enabled=False
"""
def __init__(self, path = SYSRESTORE_PATH, state_file = SYSRESTORE_STATEFILE):
"""Create a StateFile object, loading from @path.
The dictionary @modules, a member of the returned object,
is where the state can be modified. @modules is indexed
using a module name to return another dictionary containing
key/value pairs with the saved state of that module.
The keys in these latter dictionaries are arbitrary strings
and the values may either be strings or booleans.
"""
self._path = os.path.join(path, state_file)
self.modules = {}
self._load()
def _load(self):
"""Load the modules from the file @_path. @modules will
be an empty dictionary if the file doesn't exist.
"""
logger.debug("Loading StateFile from '%s'", self._path)
self.modules = {}
p = SafeConfigParser()
p.optionxform = str
p.read(self._path)
for module in p.sections():
self.modules[module] = {}
for (key, value) in p.items(module):
if value == str(True):
value = True
elif value == str(False):
value = False
self.modules[module][key] = value
def save(self):
"""Save the modules to @_path. If @modules is an empty
dict, then @_path should be removed.
"""
logger.debug("Saving StateFile to '%s'", self._path)
for module in list(self.modules):
if len(self.modules[module]) == 0:
del self.modules[module]
if len(self.modules) == 0:
logger.debug(" -> no modules, removing file")
if os.path.exists(self._path):
os.remove(self._path)
return
p = SafeConfigParser()
p.optionxform = str
for module in self.modules:
p.add_section(module)
for (key, value) in self.modules[module].items():
p.set(module, key, str(value))
with open(self._path, "w") as f:
p.write(f)
def backup_state(self, module, key, value):
"""Backup an item of system state from @module, identified
by the string @key and with the value @value. @value may be
a string or boolean.
"""
if not isinstance(value, (str, bool, unicode)):
raise ValueError("Only strings, booleans or unicode strings are supported")
self._load()
if module not in self.modules:
self.modules[module] = {}
if key not in self.modules:
self.modules[module][key] = value
self.save()
def get_state(self, module, key):
"""Return the value of an item of system state from @module,
identified by the string @key.
If the item doesn't exist, #None will be returned, otherwise
the original string or boolean value is returned.
"""
self._load()
if module not in self.modules:
return None
return self.modules[module].get(key, None)
def delete_state(self, module, key):
"""Delete system state from @module, identified by the string
@key.
If the item doesn't exist, no change is done.
"""
self._load()
try:
del self.modules[module][key]
except KeyError:
pass
else:
self.save()
def restore_state(self, module, key):
"""Return the value of an item of system state from @module,
identified by the string @key, and remove it from the backed
up system state.
If the item doesn't exist, #None will be returned, otherwise
the original string or boolean value is returned.
"""
value = self.get_state(module, key)
if value is not None:
self.delete_state(module, key)
return value
def has_state(self, module):
"""Return True or False if there is any state stored for @module.
Can be used to determine if a service is configured.
"""
return module in self.modules

View File

@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: ipalib
Version: 4.8.10
Version: 4.7.2
Summary: FreeIPA common python library
Home-page: https://www.freeipa.org/
Author: FreeIPA Developers
@@ -16,9 +16,10 @@ Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Classifier: Programming Language :: C
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Operating System :: POSIX
Classifier: Operating System :: POSIX :: Linux
@@ -26,5 +27,5 @@ Classifier: Operating System :: Unix
Classifier: Topic :: Internet :: Name Service (DNS)
Classifier: Topic :: Security
Classifier: Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP
Requires-Python: >=3.6.0
Requires-Python: >=2.7.5,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*
Provides-Extra: install

View File

@@ -9,7 +9,6 @@ constants.py
crud.py
dns.py
errors.py
facts.py
frontend.py
krb_utils.py
messages.py
@@ -22,15 +21,12 @@ request.py
rpc.py
setup.cfg
setup.py
sysrestore.py
text.py
util.py
x509.py
../COPYING
install/__init__.py
install/certmonger.py
install/certstore.py
install/dnsforwarders.py
install/hostname.py
install/kinit.py
install/service.py

View File

@@ -1,9 +1,9 @@
ipaplatform==4.8.10
ipapython==4.8.10
ipaplatform==4.7.2
ipapython==4.7.2
netaddr
pyasn1
pyasn1-modules
six
[install]
dbus-python
ipaplatform

View File

@@ -50,7 +50,7 @@ def add_message(version, result, message):
def process_message_arguments(obj, format=None, message=None, **kw):
for key, value in kw.items():
if not isinstance(value, int):
if not isinstance(value, six.integer_types):
try:
kw[key] = unicode(value)
except UnicodeError:
@@ -71,7 +71,7 @@ def process_message_arguments(obj, format=None, message=None, **kw):
obj.format = format
obj.forwarded = False
obj.msg = obj.format % kw
if isinstance(obj.format, str):
if isinstance(obj.format, six.string_types):
obj.strerror = ugettext(obj.format) % kw
else:
obj.strerror = obj.format % kw
@@ -487,15 +487,6 @@ class FailedToAddHostDNSRecords(PublicMessage):
"%(reason)s")
class LightweightCACertificateNotAvailable(PublicMessage):
"""
**13031** Certificate is not available
"""
errno = 13031
type = "error"
format = _("The certificate for %(ca)s is not available on this server.")
def iter_messages(variables, base):
"""Return a tuple with all subclasses
"""

View File

@@ -103,13 +103,12 @@ import re
import decimal
import base64
import datetime
import inspect
import typing
from xmlrpc.client import MAXINT, MININT
import six
# pylint: disable=import-error
from six.moves.xmlrpc_client import MAXINT, MININT
# pylint: enable=import-error
from cryptography import x509 as crypto_x509
import dns.name
from ipalib.text import _ as ugettext
from ipalib.base import check_name
@@ -133,7 +132,7 @@ from ipapython.dnsutil import DNSName
def _is_null(value):
if value:
return False
elif isinstance(value, (int, float, decimal.Decimal)):
elif isinstance(value, six.integer_types + (float, decimal.Decimal)):
# 0 is not NULL
return False
else:
@@ -416,8 +415,8 @@ class Param(ReadOnly):
('cli_name', str, None),
('cli_short_name', str, None),
('deprecated_cli_aliases', frozenset, frozenset()),
('label', (str, Gettext), None),
('doc', (str, Gettext), None),
('label', (six.string_types, Gettext), None),
('doc', (six.string_types, Gettext), None),
('required', bool, True),
('multivalue', bool, False),
('primary_key', bool, False),
@@ -560,7 +559,7 @@ class Param(ReadOnly):
# Check that all the rules are callable
self.class_rules = tuple(class_rules)
self.rules = rules
if self.query: # pylint: disable=using-constant-test
if self.query:
# by definition a query enforces no class or parameter rules
self.all_rules = ()
else:
@@ -598,7 +597,7 @@ class Param(ReadOnly):
value = self.__kw[key]
if callable(value) and hasattr(value, '__name__'):
value = value.__name__
elif isinstance(value, int):
elif isinstance(value, six.integer_types):
value = str(value)
elif isinstance(value, (tuple, set, frozenset)):
value = apirepr(list(value))
@@ -762,10 +761,10 @@ class Param(ReadOnly):
:param value: A proposed value for this parameter.
"""
if self.multivalue: # pylint: disable=using-constant-test
if self.multivalue:
if type(value) not in (tuple, list):
value = (value,)
if self.multivalue: # pylint: disable=using-constant-test
if self.multivalue:
return tuple(
self._normalize_scalar(v) for v in value
)
@@ -841,7 +840,7 @@ class Param(ReadOnly):
if _is_null(value):
return
if self.multivalue: # pylint: disable=using-constant-test
if self.multivalue:
if type(value) not in (tuple, list):
value = (value,)
values = tuple(
@@ -873,10 +872,10 @@ class Param(ReadOnly):
if self.required or (supplied and 'nonempty' in self.flags):
raise RequirementError(name=self.name)
return
if self.deprecated: # pylint: disable=using-constant-test
if self.deprecated:
raise ValidationError(name=self.get_param_name(),
error=_('this option is deprecated'))
if self.multivalue: # pylint: disable=using-constant-test
if self.multivalue:
if type(value) is not tuple:
raise TypeError(
TYPE_ERROR % ('value', tuple, value, type(value))
@@ -970,8 +969,8 @@ class Param(ReadOnly):
for a, k, _d in self.kwargs:
if k in (callable, DefaultFrom):
continue
if isinstance(getattr(self, a), frozenset):
json_dict[a] = list(getattr(self, a, []))
elif isinstance(getattr(self, a), frozenset):
json_dict[a] = [k for k in getattr(self, a, [])]
else:
val = getattr(self, a, '')
if val is None:
@@ -1008,7 +1007,7 @@ class Bool(Param):
"""
if type(value) in self.allowed_types:
return value
if isinstance(value, str):
if isinstance(value, six.string_types):
value = value.lower()
if value in self.truths:
return True
@@ -1073,7 +1072,7 @@ class Number(Param):
"""
if type(value) in self.allowed_types:
return value
if type(value) in (unicode, float, int):
if type(value) in (unicode, float) + six.integer_types:
try:
return self.type(value)
except ValueError:
@@ -1090,12 +1089,12 @@ class Int(Number):
"""
type = int
allowed_types = (int,)
allowed_types = six.integer_types
type_error = _('must be an integer')
kwargs = Param.kwargs + (
('minvalue', int, int(MININT)),
('maxvalue', int, int(MAXINT)),
('minvalue', six.integer_types, int(MININT)),
('maxvalue', six.integer_types, int(MAXINT)),
)
@staticmethod
@@ -1139,7 +1138,7 @@ class Int(Number):
"""
Check min constraint.
"""
assert isinstance(value, int)
assert type(value) in six.integer_types
if value < self.minvalue:
return _('must be at least %(minvalue)d') % dict(
minvalue=self.minvalue,
@@ -1151,7 +1150,7 @@ class Int(Number):
"""
Check max constraint.
"""
assert isinstance(value, int)
assert type(value) in six.integer_types
if value > self.maxvalue:
return _('can be at most %(maxvalue)d') % dict(
maxvalue=self.maxvalue,
@@ -1189,7 +1188,7 @@ class Decimal(Number):
value = kw.get(kwparam)
if value is None:
continue
if isinstance(value, (str, float)):
if isinstance(value, (six.string_types, float)):
try:
value = decimal.Decimal(value)
except Exception as e:
@@ -1283,7 +1282,7 @@ class Decimal(Number):
return value
def _convert_scalar(self, value, index=None):
if isinstance(value, (str, float)):
if isinstance(value, (six.string_types, float)):
try:
value = decimal.Decimal(value)
except decimal.DecimalException as e:
@@ -1314,7 +1313,7 @@ class Data(Param):
('minlength', int, None),
('maxlength', int, None),
('length', int, None),
('pattern_errmsg', (str,), None),
('pattern_errmsg', (six.string_types,), None),
)
re = None
@@ -1543,7 +1542,7 @@ class Str(Data):
"""
kwargs = Data.kwargs + (
('pattern', (str,), None),
('pattern', (six.string_types,), None),
('noextrawhitespace', bool, True),
)
@@ -1564,7 +1563,7 @@ class Str(Data):
"""
if type(value) in self.allowed_types:
return value
if type(value) in (int, float, decimal.Decimal):
if type(value) in (float, decimal.Decimal) + six.integer_types:
return self.type(value)
if type(value) in (tuple, list):
raise ConversionError(name=self.name,
@@ -1631,7 +1630,7 @@ class IA5Str(Str):
super(IA5Str, self).__init__(name, *rules, **kw)
def _convert_scalar(self, value, index=None):
if isinstance(value, str):
if isinstance(value, six.string_types):
for char in value:
if ord(char) > 127:
raise ConversionError(name=self.get_param_name(),
@@ -1722,7 +1721,7 @@ class IntEnum(Enum):
"""
type = int
allowed_types = (int,)
allowed_types = six.integer_types
type_error = Int.type_error
def _convert_scalar(self, value, index=None):
@@ -1806,7 +1805,7 @@ class DateTime(Param):
type_error = _('must be datetime value')
def _convert_scalar(self, value, index=None):
if isinstance(value, str):
if isinstance(value, six.string_types):
if value == u'now':
time = datetime.datetime.utcnow()
return time
@@ -2158,67 +2157,3 @@ class Principal(Param):
name=self.get_param_name(),
error=_("Service principal is required")
)
_map_types = {
# map internal certificate subclass to generic cryptography class
IPACertificate: crypto_x509.Certificate,
# map internal DNS name class to generic dnspython class
DNSName: dns.name.Name,
# DN, Principal have their names mangled in ipaapi.__init__
}
def create_signature(command):
"""Create an inspect.Signature for a command
:param command: ipa plugin instance (server or client)
:return: inspect.Signature instance
"""
signature_params = []
seen = set()
args_options = [
(command.get_args(), inspect.Parameter.POSITIONAL_OR_KEYWORD),
(command.get_options(), inspect.Parameter.KEYWORD_ONLY)
]
for ipaparams, kind in args_options:
for ipaparam in ipaparams:
# filter out duplicates, for example user_del has a preserve flag
# and preserve bool.
if ipaparam.name in seen:
continue
seen.add(ipaparam.name)
# ipalib.plugins.misc.env has wrong type
if not isinstance(ipaparam, Param):
continue
if ipaparam.required:
default = inspect.Parameter.empty
else:
default = ipaparam.default
allowed_types = tuple(
_map_types.get(t, t) for t in ipaparam.allowed_types
)
# ipalib.parameters.DNSNameParam also handles text
if isinstance(ipaparam, DNSNameParam):
allowed_types += (six.text_type,)
ann = typing.Union[allowed_types]
if ipaparam.multivalue:
ann = typing.List[ann]
signature_params.append(
inspect.Parameter(
ipaparam.name, kind, default=default, annotation=ann
)
)
# cannot describe return parameter with typing yet. TypedDict
# is only available with mypy_extension.
signature = inspect.Signature(
signature_params,
return_annotation=typing.Dict[typing.Text, typing.Any]
)
return signature

View File

@@ -24,7 +24,6 @@ The classes in this module make heavy use of Python container emulation. If
you are unfamiliar with this Python feature, see
http://docs.python.org/ref/sequence-types.html
"""
import logging
import operator
import re
@@ -47,6 +46,7 @@ from ipalib.base import ReadOnly, lock, islocked
from ipalib.constants import DEFAULT_CONFIG
from ipapython import ipa_log_manager, ipautil
from ipapython.ipa_log_manager import (
log_mgr,
LOGGING_FORMAT_FILE,
LOGGING_FORMAT_STDERR)
from ipapython.version import VERSION, API_VERSION, DEFAULT_PLUGINS
@@ -89,7 +89,7 @@ def find_modules_in_dir(src_dir):
yield module
class Registry:
class Registry(object):
"""A decorator that makes plugins available to the API
Usage::
@@ -144,6 +144,7 @@ class Plugin(ReadOnly):
self.__finalize_called = False
self.__finalized = False
self.__finalize_lock = threading.RLock()
log_mgr.get_logger(self, True)
@classmethod
def __name_getter(cls):
@@ -174,7 +175,7 @@ class Plugin(ReadOnly):
def __summary_getter(cls):
doc = cls.doc
if not _(doc).msg:
return '<%s.%s>' % (cls.__module__, cls.__name__)
return u'<%s.%s>' % (cls.__module__, cls.__name__)
else:
return unicode(doc).split('\n\n', 1)[0].strip()
@@ -238,7 +239,7 @@ class Plugin(ReadOnly):
if not self.__finalized:
self.finalize()
class finalize_attr:
class finalize_attr(object):
"""
Create a stub object for plugin attribute that isn't set until the
finalization of the plugin initialization.
@@ -326,14 +327,6 @@ class APINameSpace(Mapping):
self.__enumerate()
return iter(self.__plugins)
def __dir__(self):
# include plugins for readline tab completion and in dir()
self.__enumerate()
names = super().__dir__()
names.extend(p.name for p in self)
names.sort()
return names
def get_plugin(self, key):
self.__enumerate()
return self.__plugins_by_key[key]
@@ -445,6 +438,7 @@ class API(ReadOnly):
Initialize environment variables and logging.
"""
self.__doing('bootstrap')
self.log = log_mgr.get_logger(self)
self.env._bootstrap(**overrides)
self.env._finalize_core(**dict(DEFAULT_CONFIG))
@@ -460,7 +454,7 @@ class API(ReadOnly):
if root_logger.handlers or self.env.validate_api:
return
if self.env.debug: # pylint: disable=using-constant-test
if self.env.debug:
level = logging.DEBUG
else:
level = logging.INFO
@@ -476,7 +470,7 @@ class API(ReadOnly):
level = ipa_log_manager.convert_log_level(match.group(1))
value = getattr(self.env, attr)
regexps = re.split(r'\s*,\s*', value)
regexps = re.split('\s*,\s*', value)
# Add the regexp, it maps to the configured level
for regexp in regexps:
@@ -484,7 +478,7 @@ class API(ReadOnly):
# Add stderr handler:
level = logging.INFO
if self.env.debug: # pylint: disable=using-constant-test
if self.env.debug:
level = logging.DEBUG
else:
if self.env.context == 'cli':
@@ -516,7 +510,7 @@ class API(ReadOnly):
return
level = logging.INFO
if self.env.debug: # pylint: disable=using-constant-test
if self.env.debug:
level = logging.DEBUG
try:
handler = logging.FileHandler(self.env.log)
@@ -597,18 +591,13 @@ class API(ReadOnly):
assert type(options.env) is list
for item in options.env:
try:
values = item.split('=', 1)
(key, value) = item.split('=', 1)
except ValueError:
# FIXME: this should raise an IPA exception with an
# error code.
# --Jason, 2008-10-31
pass
if len(values) == 2:
(key, value) = values
overrides[str(key.strip())] = value.strip()
else:
raise errors.OptionError(_('Unable to parse option {item}'
.format(item=item)))
overrides[str(key.strip())] = value.strip()
for key in ('conf', 'debug', 'verbose', 'prompt_all', 'interactive',
'fallback', 'delegate'):
value = getattr(options, key, None)
@@ -665,8 +654,7 @@ class API(ReadOnly):
logger.debug("skipping plugin module %s: %s", name, e.reason)
continue
except Exception as e:
tb = self.env.startup_traceback
if tb: # pylint: disable=using-constant-test
if self.env.startup_traceback:
logger.exception("could not load plugin module %s", name)
raise

View File

@@ -33,7 +33,7 @@ from ipalib.constants import CALLABLE_ERROR
context = threading.local()
class _FrameContext:
class _FrameContext(object):
pass

View File

@@ -42,17 +42,17 @@ import json
import re
import socket
import gzip
import urllib
from ssl import SSLError
from cryptography import x509 as crypto_x509
import gssapi
from dns.exception import DNSException
from ssl import SSLError
import six
from six.moves import urllib
from ipalib.backend import Connectible
from ipalib.constants import LDAP_GENERALIZED_TIME_FORMAT
from ipalib.errors import (errors_by_code, UnknownError, NetworkError,
from ipalib.errors import (public_errors, UnknownError, NetworkError,
XMLRPCMarshallError, JSONError)
from ipalib import errors, capabilities
from ipalib.request import context, Connection
@@ -97,6 +97,8 @@ logger = logging.getLogger(__name__)
COOKIE_NAME = 'ipa_session'
CCACHE_COOKIE_KEY = 'X-IPA-Session-Cookie'
errors_by_code = dict((e.errno, e) for e in public_errors)
def update_persistent_client_session_data(principal, data):
'''
@@ -169,7 +171,7 @@ def xml_wrap(value, version):
if type(value) is Decimal:
# transfer Decimal as a string
return unicode(value)
if isinstance(value, int) and (value < MININT or value > MAXINT):
if isinstance(value, six.integer_types) and (value < MININT or value > MAXINT):
return unicode(value)
if isinstance(value, DN):
return str(value)
@@ -198,7 +200,7 @@ def xml_wrap(value, version):
return base64.b64encode(
value.public_bytes(x509_Encoding.DER)).decode('ascii')
assert type(value) in (unicode, float, int, bool, type(None))
assert type(value) in (unicode, float, bool, type(None)) + six.integer_types
return value
@@ -318,7 +320,6 @@ class _JSONPrimer(dict):
self.update({
unicode: _identity,
bool: _identity,
int: _identity,
type(None): _identity,
float: _identity,
Decimal: unicode,
@@ -333,6 +334,9 @@ class _JSONPrimer(dict):
crypto_x509.Certificate: self._enc_certificate,
crypto_x509.CertificateSigningRequest: self._enc_certificate,
})
# int, long
for t in six.integer_types:
self[t] = _identity
def __missing__(self, typ):
# walk MRO to find best match
@@ -487,7 +491,7 @@ def xml_loads(data, encoding='UTF-8'):
raise decode_fault(e)
class DummyParser:
class DummyParser(object):
def __init__(self):
self.data = []
@@ -1168,11 +1172,11 @@ class RPCClient(Connectible):
try:
principal = getattr(context, 'principal', None)
delete_persistent_client_session_data(principal)
except Exception as e2:
except Exception as e:
# This shouldn't happen if we have a session
# but it isn't fatal.
logger.debug("Error trying to remove persisent "
"session data: %s", e2)
"session data: %s", e)
# Create a new serverproxy with the non-session URI
serverproxy = self.create_connection(
@@ -1206,7 +1210,7 @@ class xmlclient(RPCClient):
return xml_unwrap(result)
class JSONServerProxy:
class JSONServerProxy(object):
def __init__(self, uri, transport, encoding, verbose, allow_none):
split_uri = urllib.parse.urlsplit(uri)
if split_uri.scheme not in ("http", "https"):

View File

@@ -44,6 +44,6 @@ if __name__ == '__main__':
"six",
],
extras_require={
"install": ["dbus-python"], # for certmonger and resolve1
"install": ["ipaplatform"],
},
)

View File

@@ -1,457 +0,0 @@
# Authors: Mark McLoughlin <markmc@redhat.com>
#
# Copyright (C) 2007 Red Hat
# see file 'COPYING' for use and warranty information
#
# 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 3 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, see <http://www.gnu.org/licenses/>.
#
#
# This module provides a very simple API which allows
# ipa-xxx-install --uninstall to restore certain
# parts of the system configuration to the way it was
# before ipa-server-install was first run
from __future__ import absolute_import
import logging
import os
import os.path
import shutil
import random
from hashlib import sha256
import six
# pylint: disable=import-error
if six.PY3:
# The SafeConfigParser class has been renamed to ConfigParser in Py3
from configparser import ConfigParser as SafeConfigParser
else:
from ConfigParser import SafeConfigParser
# pylint: enable=import-error
from ipaplatform.tasks import tasks
from ipaplatform.paths import paths
if six.PY3:
unicode = str
logger = logging.getLogger(__name__)
SYSRESTORE_PATH = paths.TMP
SYSRESTORE_INDEXFILE = "sysrestore.index"
SYSRESTORE_STATEFILE = "sysrestore.state"
class FileStore:
"""Class for handling backup and restore of files"""
def __init__(self, path=SYSRESTORE_PATH, index_file=SYSRESTORE_INDEXFILE):
"""Create a _StoreFiles object, that uses @path as the
base directory.
The file @path/sysrestore.index is used to store information
about the original location of the saved files.
"""
self._path = path
self._index = os.path.join(self._path, index_file)
self.random = random.Random()
self.files = {}
self._load()
def _load(self):
"""Load the file list from the index file. @files will
be an empty dictionary if the file doesn't exist.
"""
logger.debug("Loading Index file from '%s'", self._index)
self.files = {}
p = SafeConfigParser()
p.optionxform = str
p.read(self._index)
for section in p.sections():
if section == "files":
for (key, value) in p.items(section):
self.files[key] = value
def save(self):
"""Save the file list to @_index. If @files is an empty
dict, then @_index should be removed.
"""
logger.debug("Saving Index File to '%s'", self._index)
if len(self.files) == 0:
logger.debug(" -> no files, removing file")
if os.path.exists(self._index):
os.remove(self._index)
return
p = SafeConfigParser()
p.optionxform = str
p.add_section('files')
for (key, value) in self.files.items():
p.set('files', key, str(value))
with open(self._index, "w") as f:
p.write(f)
def backup_file(self, path):
"""Create a copy of the file at @path - as long as an exact copy
does not already exist - which will be restored to its
original location by restore_files().
"""
logger.debug("Backing up system configuration file '%s'", path)
if not os.path.isabs(path):
raise ValueError("Absolute path required")
if not os.path.isfile(path):
logger.debug(" -> Not backing up - '%s' doesn't exist", path)
return
_reldir, backupfile = os.path.split(path)
with open(path, 'rb') as f:
cont_hash = sha256(f.read()).hexdigest()
filename = "{hexhash}-{bcppath}".format(hexhash=cont_hash,
bcppath=backupfile)
backup_path = os.path.join(self._path, filename)
if os.path.exists(backup_path):
logger.debug(" -> Not backing up - already have a copy of '%s'",
path)
return
shutil.copy2(path, backup_path)
stat = os.stat(path)
template = '{stat.st_mode},{stat.st_uid},{stat.st_gid},{path}'
self.files[filename] = template.format(stat=stat, path=path)
self.save()
def has_file(self, path):
"""Checks whether file at @path was added to the file store
Returns #True if the file exists in the file store, #False otherwise
"""
result = False
for _key, value in self.files.items():
_mode, _uid, _gid, filepath = value.split(',', 3)
if (filepath == path):
result = True
break
return result
def restore_file(self, path, new_path=None):
"""Restore the copy of a file at @path to its original
location and delete the copy.
Takes optional parameter @new_path which specifies the
location where the file is to be restored.
Returns #True if the file was restored, #False if there
was no backup file to restore
"""
if new_path is None:
logger.debug("Restoring system configuration file '%s'",
path)
else:
logger.debug("Restoring system configuration file '%s' to '%s'",
path, new_path)
if not os.path.isabs(path):
raise ValueError("Absolute path required")
if new_path is not None and not os.path.isabs(new_path):
raise ValueError("Absolute new path required")
mode = None
uid = None
gid = None
filename = None
for (key, value) in self.files.items():
(mode,uid,gid,filepath) = value.split(',', 3)
if (filepath == path):
filename = key
break
if not filename:
raise ValueError("No such file name in the index")
backup_path = os.path.join(self._path, filename)
if not os.path.exists(backup_path):
logger.debug(" -> Not restoring - '%s' doesn't exist",
backup_path)
return False
if new_path is not None:
path = new_path
shutil.copy(backup_path, path) # SELinux needs copy
os.remove(backup_path)
os.chown(path, int(uid), int(gid))
os.chmod(path, int(mode))
tasks.restore_context(path)
del self.files[filename]
self.save()
return True
def restore_all_files(self):
"""Restore the files in the inbdex to their original
location and delete the copy.
Returns #True if the file was restored, #False if there
was no backup file to restore
"""
if len(self.files) == 0:
return False
for (filename, value) in self.files.items():
(mode,uid,gid,path) = value.split(',', 3)
backup_path = os.path.join(self._path, filename)
if not os.path.exists(backup_path):
logger.debug(" -> Not restoring - '%s' doesn't exist",
backup_path)
continue
shutil.copy(backup_path, path) # SELinux needs copy
os.remove(backup_path)
os.chown(path, int(uid), int(gid))
os.chmod(path, int(mode))
tasks.restore_context(path)
# force file to be deleted
self.files = {}
self.save()
return True
def has_files(self):
"""Return True or False if there are any files in the index
Can be used to determine if a program is configured.
"""
return len(self.files) > 0
def untrack_file(self, path):
"""Remove file at path @path from list of backed up files.
Does not remove any files from the filesystem.
Returns #True if the file was untracked, #False if there
was no backup file to restore
"""
logger.debug("Untracking system configuration file '%s'", path)
if not os.path.isabs(path):
raise ValueError("Absolute path required")
filename = None
for (key, value) in self.files.items():
_mode, _uid, _gid, filepath = value.split(',', 3)
if (filepath == path):
filename = key
break
if not filename:
raise ValueError("No such file name in the index")
backup_path = os.path.join(self._path, filename)
if not os.path.exists(backup_path):
logger.debug(" -> Not restoring - '%s' doesn't exist",
backup_path)
return False
try:
os.unlink(backup_path)
except Exception as e:
logger.error('Error removing %s: %s', backup_path, str(e))
del self.files[filename]
self.save()
return True
class StateFile:
"""A metadata file for recording system state which can
be backed up and later restored.
StateFile gets reloaded every time to prevent loss of information
recorded by child processes. But we do not solve concurrency
because there is no need for it right now.
The format is something like:
[httpd]
running=True
enabled=False
"""
def __init__(self, path=SYSRESTORE_PATH, state_file=SYSRESTORE_STATEFILE):
"""Create a StateFile object, loading from @path.
The dictionary @modules, a member of the returned object,
is where the state can be modified. @modules is indexed
using a module name to return another dictionary containing
key/value pairs with the saved state of that module.
The keys in these latter dictionaries are arbitrary strings
and the values may either be strings or booleans.
"""
self._path = os.path.join(path, state_file)
self.modules = {}
self._load()
def _load(self):
"""Load the modules from the file @_path. @modules will
be an empty dictionary if the file doesn't exist.
"""
logger.debug("Loading StateFile from '%s'", self._path)
self.modules = {}
p = SafeConfigParser()
p.optionxform = str
p.read(self._path)
for module in p.sections():
self.modules[module] = {}
for (key, value) in p.items(module):
if value == str(True):
value = True
elif value == str(False):
value = False
self.modules[module][key] = value
def save(self):
"""Save the modules to @_path. If @modules is an empty
dict, then @_path should be removed.
"""
logger.debug("Saving StateFile to '%s'", self._path)
for module in list(self.modules):
if len(self.modules[module]) == 0:
del self.modules[module]
if len(self.modules) == 0:
logger.debug(" -> no modules, removing file")
if os.path.exists(self._path):
os.remove(self._path)
return
p = SafeConfigParser()
p.optionxform = str
for module in self.modules:
p.add_section(module)
for (key, value) in self.modules[module].items():
p.set(module, key, str(value))
with open(self._path, "w") as f:
p.write(f)
def backup_state(self, module, key, value):
"""Backup an item of system state from @module, identified
by the string @key and with the value @value. @value may be
a string or boolean.
"""
if not isinstance(value, (str, bool, unicode)):
raise ValueError("Only strings, booleans or unicode strings "
"are supported")
self._load()
if module not in self.modules:
self.modules[module] = {}
if key not in self.modules:
self.modules[module][key] = value
self.save()
def get_state(self, module, key):
"""Return the value of an item of system state from @module,
identified by the string @key.
If the item doesn't exist, #None will be returned, otherwise
the original string or boolean value is returned.
"""
self._load()
if module not in self.modules:
return None
return self.modules[module].get(key, None)
def delete_state(self, module, key):
"""Delete system state from @module, identified by the string
@key.
If the item doesn't exist, no change is done.
"""
self._load()
try:
del self.modules[module][key]
except KeyError:
pass
else:
self.save()
def restore_state(self, module, key):
"""Return the value of an item of system state from @module,
identified by the string @key, and remove it from the backed
up system state.
If the item doesn't exist, #None will be returned, otherwise
the original string or boolean value is returned.
"""
value = self.get_state(module, key)
if value is not None:
self.delete_state(module, key)
return value
def has_state(self, module):
"""Return True or False if there is any state stored for @module.
Can be used to determine if a service is configured.
"""
return module in self.modules

View File

@@ -154,7 +154,7 @@ def create_translation(key):
return translation
class LazyText:
class LazyText(object):
"""
Base class for deferred translation.
@@ -319,7 +319,7 @@ class FixMe(Gettext):
creates conspicuous looking UI labels that remind the programmer to
"fix-me!". For example, the typical usage would be something like this:
>>> class Plugin:
>>> class Plugin(object):
... label = None
... def __init__(self):
... self.name = self.__class__.__name__
@@ -485,7 +485,7 @@ class NGettext(LazyText):
@six.python_2_unicode_compatible
class ConcatenatedLazyText:
class ConcatenatedLazyText(object):
"""Concatenation of multiple strings, or any objects convertible to unicode
Used to concatenate several LazyTexts together.
@@ -525,7 +525,7 @@ class ConcatenatedLazyText:
return ConcatenatedLazyText(*[other] + self.components)
class GettextFactory:
class GettextFactory(object):
"""
Factory for creating ``_()`` functions.

View File

@@ -37,12 +37,11 @@ import sys
import ssl
import termios
import fcntl
import shutil
import struct
import subprocess
import netaddr
from dns import rdatatype
from dns import resolver, rdatatype
from dns.exception import DNSException
from dns.resolver import NXDOMAIN
from netaddr.core import AddrFormatError
@@ -57,22 +56,15 @@ except ImportError:
from ipalib import errors, messages
from ipalib.constants import (
DOMAIN_LEVEL_0,
TLS_VERSIONS, TLS_VERSION_MINIMAL, TLS_VERSION_MAXIMAL,
TLS_VERSION_DEFAULT_MIN, TLS_VERSION_DEFAULT_MAX,
TLS_VERSIONS, TLS_VERSION_MINIMAL
)
# pylint: disable=ipa-forbidden-import
from ipalib.facts import is_ipa_client_configured
from ipalib.text import _
from ipaplatform.constants import constants
from ipaplatform.paths import paths
from ipapython.ssh import SSHPublicKey
from ipapython.dn import DN, RDN
from ipapython.dnsutil import (
DNSName,
DNSResolver,
resolve,
resolve_ip_addresses,
)
from ipapython.dnsutil import DNSName
from ipapython.dnsutil import resolve_ip_addresses
from ipapython.admintool import ScriptError
if sys.version_info >= (3, 2):
@@ -94,7 +86,7 @@ def json_serialize(obj):
return [json_serialize(o) for o in obj]
if isinstance(obj, dict):
return {k: json_serialize(v) for (k, v) in obj.items()}
if isinstance(obj, (int, bool, float, unicode, type(None))):
if isinstance(obj, (bool, float, unicode, type(None), six.integer_types)):
return obj
if isinstance(obj, str):
return obj.decode('utf-8')
@@ -121,13 +113,13 @@ def has_soa_or_ns_record(domain):
Returns True or False.
"""
try:
resolve(domain, rdatatype.SOA)
resolver.query(domain, rdatatype.SOA)
soa_record_found = True
except DNSException:
soa_record_found = False
try:
resolve(domain, rdatatype.NS)
resolver.query(domain, rdatatype.NS)
ns_record_found = True
except DNSException:
ns_record_found = False
@@ -222,7 +214,7 @@ def check_writable_file(filename):
raise errors.FileError(reason=str(e))
def normalize_zonemgr(zonemgr):
if not zonemgr or not isinstance(zonemgr, str):
if not zonemgr or not isinstance(zonemgr, six.string_types):
return zonemgr
if '@' in zonemgr:
# local-part needs to be normalized
@@ -254,13 +246,6 @@ def get_proper_tls_version_span(tls_version_min, tls_version_max):
if lower than TLS_VERSION_MINIMAL
:raises: ValueError
"""
if tls_version_min is None and tls_version_max is None:
# no defaults, use system's default TLS version range
return None
if tls_version_min is None:
tls_version_min = TLS_VERSION_MINIMAL
if tls_version_max is None:
tls_version_max = TLS_VERSION_MAXIMAL
min_allowed_idx = TLS_VERSIONS.index(TLS_VERSION_MINIMAL)
try:
@@ -296,8 +281,8 @@ def create_https_connection(
cafile=None,
client_certfile=None, client_keyfile=None,
keyfile_passwd=None,
tls_version_min=TLS_VERSION_DEFAULT_MIN,
tls_version_max=TLS_VERSION_DEFAULT_MAX,
tls_version_min="tls1.1",
tls_version_max="tls1.2",
**kwargs
):
"""
@@ -327,7 +312,6 @@ def create_https_connection(
"tls1.0": ssl.OP_NO_TLSv1,
"tls1.1": ssl.OP_NO_TLSv1_1,
"tls1.2": ssl.OP_NO_TLSv1_2,
"tls1.3": getattr(ssl, "OP_NO_TLSv1_3", 0),
}
# pylint: enable=no-member
@@ -339,6 +323,9 @@ def create_https_connection(
raise RuntimeError("cafile \'{file}\' doesn't exist or is unreadable".
format(file=cafile))
# remove the slice of negating protocol options according to options
tls_span = get_proper_tls_version_span(tls_version_min, tls_version_max)
# official Python documentation states that the best option to get
# TLSv1 and later is to setup SSLContext with PROTOCOL_SSLv23
# and then negate the insecure SSLv2 and SSLv3
@@ -349,28 +336,20 @@ def create_https_connection(
ssl.OP_SINGLE_ECDH_USE
)
if constants.TLS_HIGH_CIPHERS is not None:
# configure ciphers, uses system crypto policies on RH platforms.
ctx.set_ciphers(constants.TLS_HIGH_CIPHERS)
# remove the slice of negating protocol options according to options
tls_span = get_proper_tls_version_span(tls_version_min, tls_version_max)
# high ciphers without RC4, MD5, TripleDES, pre-shared key and secure
# remote password. Uses system crypto policies on some platforms.
ctx.set_ciphers(constants.TLS_HIGH_CIPHERS)
# pylint: enable=no-member
# set up the correct TLS version flags for the SSL context
if tls_span is not None:
for version in TLS_VERSIONS:
if version in tls_span:
# make sure the required TLS versions are available if Python
# decides to modify the default TLS flags
ctx.options &= ~tls_cutoff_map[version]
else:
# disable all TLS versions not in tls_span
ctx.options |= tls_cutoff_map[version]
# Enable TLS 1.3 post-handshake auth
if getattr(ctx, "post_handshake_auth", None) is not None:
ctx.post_handshake_auth = True
for version in TLS_VERSIONS:
if version in tls_span:
# make sure the required TLS versions are available if Python
# decides to modify the default TLS flags
ctx.options &= ~tls_cutoff_map[version]
else:
# disable all TLS versions not in tls_span
ctx.options |= tls_cutoff_map[version]
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.check_hostname = True
@@ -454,28 +433,14 @@ def validate_zonemgr_str(zonemgr):
zonemgr = DNSName(zonemgr)
return validate_zonemgr(zonemgr)
def validate_hostname(hostname, check_fqdn=True, allow_underscore=False,
allow_slash=False, maxlen=255):
def validate_hostname(hostname, check_fqdn=True, allow_underscore=False, allow_slash=False):
""" See RFC 952, 1123
Length limit of 64 imposed by MAXHOSTNAMELEN on Linux.
DNS and other operating systems has a max length of 255. Default to
the theoretical max unless explicitly told to limit. The cases
where a limit would be set might include:
* *-install --hostname
* ipa host-add
The *-install commands by definition are executed on Linux hosts so
the maximum length needs to be limited.
:param hostname Checked value
:param check_fqdn Check if hostname is fully qualified
"""
if len(hostname) > maxlen:
raise ValueError(_('cannot be longer that {} characters'.format(
maxlen)))
if len(hostname) > 255:
raise ValueError(_('cannot be longer that 255 characters'))
if hostname.endswith('.'):
hostname = hostname[:-1]
@@ -798,10 +763,10 @@ def _resolve_record(owner, rtype, nameserver_ip=None, edns0=False,
:param flag_cd: requires dnssec=True, adds flag CD
:raise DNSException: if error occurs
"""
assert isinstance(nameserver_ip, str) or nameserver_ip is None
assert isinstance(rtype, str)
assert isinstance(nameserver_ip, six.string_types) or nameserver_ip is None
assert isinstance(rtype, six.string_types)
res = DNSResolver()
res = dns.resolver.Resolver()
if nameserver_ip:
res.nameservers = [nameserver_ip]
res.lifetime = timeout
@@ -819,7 +784,7 @@ def _resolve_record(owner, rtype, nameserver_ip=None, edns0=False,
elif edns0:
res.use_edns(0, 0, 4096)
return res.resolve(owner, rtype)
return res.query(owner, rtype)
def _validate_edns0_forwarder(owner, rtype, ip_addr, timeout=10):
@@ -989,7 +954,7 @@ def detect_dns_zone_realm_type(api, domain):
kerberos_record_name = kerberos_prefix + domain_suffix
try:
result = resolve(kerberos_record_name, rdatatype.TXT)
result = resolver.query(kerberos_record_name, rdatatype.TXT)
answer = result.response.answer
# IPA domain will have only one _kerberos TXT record
@@ -1016,7 +981,7 @@ def detect_dns_zone_realm_type(api, domain):
try:
# The presence of this record is enough, return foreign in such case
resolve(ad_specific_record_name, rdatatype.SRV)
resolver.query(ad_specific_record_name, rdatatype.SRV)
except DNSException:
# If we could not detect type with certainty, return unknown
return 'unknown'
@@ -1029,11 +994,10 @@ def has_managed_topology(api):
return domainlevel > DOMAIN_LEVEL_0
class classproperty:
class classproperty(object):
__slots__ = ('__doc__', 'fget')
def __init__(self, fget=None, doc=None):
assert isinstance(fget, classmethod)
if doc is None and fget is not None:
doc = fget.__doc__
@@ -1056,17 +1020,6 @@ class classproperty:
return self
class classobjectproperty(classproperty):
# A class property that also passes the object to the getter
# obj is None for class objects and 'self' for instance objects.
__slots__ = ('__doc__',)
def __get__(self, obj, obj_type):
if self.fget is not None:
return self.fget.__get__(obj, obj_type)(obj)
raise AttributeError("unreadable attribute")
def normalize_hostname(hostname):
"""Use common fqdn form without the trailing dot"""
if hostname.endswith(u'.'):
@@ -1075,16 +1028,9 @@ def normalize_hostname(hostname):
return hostname
def hostname_validator(ugettext, value, maxlen=255):
"""Validator used by plugins to ensure hostname compliance.
In Linux the maximum hostname length is 64. In DNS and
other operaring systems (Solaris) it is 255. If not explicitly
checking a Linux hostname (e.g. the server) use the DNS
default.
"""
def hostname_validator(ugettext, value):
try:
validate_hostname(value, maxlen=maxlen)
validate_hostname(value)
except ValueError as e:
return _('invalid domain-name: %s') % unicode(e)
@@ -1176,37 +1122,17 @@ def ensure_krbcanonicalname_set(ldap, entry_attrs):
entry_attrs.update(old_entry)
def check_client_configuration(env=None):
def check_client_configuration():
"""
Check if IPA client is configured on the system.
This is a convenience wrapper that also supports using
a custom configuration via IPA_CONFDIR.
Raises a ScriptError exception if the client is not
configured.
Hardcode return code to avoid recursive imports
"""
CLIENT_NOT_CONFIGURED = 2
if env is not None and env.confdir != paths.ETC_IPA:
# custom IPA conf dir, check for custom conf_default
if os.path.isfile(env.conf_default):
return True
else:
raise ScriptError(
f'IPA client is not configured on this system (confdir '
f'{env.confdir} is missing {env.conf_default})',
CLIENT_NOT_CONFIGURED
)
if is_ipa_client_configured():
return True
else:
raise ScriptError(
'IPA client is not configured on this system',
CLIENT_NOT_CONFIGURED
)
if (not os.path.isfile(paths.IPA_DEFAULT_CONF) or
not os.path.isdir(paths.IPA_CLIENT_SYSRESTORE) or
not os.listdir(paths.IPA_CLIENT_SYSRESTORE)):
raise ScriptError('IPA client is not configured on this system',
2) # CLIENT_NOT_CONFIGURED
def check_principal_realm_in_trust_namespace(api_instance, *keys):
@@ -1277,27 +1203,17 @@ def get_terminal_height(fd=1):
return os.environ.get("LINES", 25)
def get_pager():
""" Get path to a pager
:return: path to the file if it exists otherwise None
:rtype: str or None
"""
pager = os.environ.get('PAGER', 'less')
return shutil.which(pager)
def open_in_pager(data, pager):
def open_in_pager(data):
"""
Open text data in pager
Args:
data (bytes): data to view in pager
pager (str): path to the pager
Returns:
None
"""
pager = os.environ.get("PAGER", "less")
pager_process = subprocess.Popen([pager], stdin=subprocess.PIPE)
try:

View File

@@ -34,7 +34,6 @@ from __future__ import print_function
import os
import binascii
import datetime
import enum
import ipaddress
import ssl
import base64
@@ -48,7 +47,6 @@ from cryptography.hazmat.primitives.serialization import (
Encoding, PublicFormat, PrivateFormat, load_pem_private_key
)
import pyasn1
import pyasn1.error
from pyasn1.type import univ, char, namedtype, tag
from pyasn1.codec.der import decoder, encoder
from pyasn1_modules import rfc2315, rfc2459
@@ -85,7 +83,7 @@ SAN_KRB5PRINCIPALNAME = '1.3.6.1.5.2.2'
@crypto_utils.register_interface(crypto_x509.Certificate)
class IPACertificate:
class IPACertificate(object):
"""
A proxy class wrapping a python-cryptography certificate representation for
FreeIPA purposes
@@ -598,7 +596,7 @@ def write_pem_private_key(priv_key, filename, passwd=None):
os.fchmod(fp.fileno(), 0o600)
fp.write(priv_key.private_bytes(
Encoding.PEM,
PrivateFormat.PKCS8,
PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=enc_alg))
except (IOError, OSError) as e:
raise errors.FileError(reason=str(e))
@@ -747,172 +745,3 @@ def format_datetime(t):
if t.tzinfo is None:
t = t.replace(tzinfo=UTC())
return unicode(t.strftime("%a %b %d %H:%M:%S %Y %Z"))
class ExternalCAType(enum.Enum):
GENERIC = 'generic'
MS_CS = 'ms-cs'
class ExternalCAProfile:
"""
An external CA profile configuration. Currently the only
subclasses are for Microsoft CAs, for providing data in the
"Certificate Template" extension.
Constructing this class will actually return an instance of a
subclass.
Subclasses MUST set ``valid_for``.
"""
def __init__(self, s=None):
self.unparsed_input = s
# Which external CA types is the data valid for?
# A set of VALUES of the ExternalCAType enum.
valid_for = set()
def __new__(cls, s=None):
"""Construct the ExternalCAProfile value.
Return an instance of a subclass determined by
the format of the argument.
"""
# we are directly constructing a subclass; instantiate
# it and be done
if cls is not ExternalCAProfile:
return super(ExternalCAProfile, cls).__new__(cls)
# construction via the base class; therefore the string
# argument is required, and is used to determine which
# subclass to construct
if s is None:
raise ValueError('string argument is required')
parts = s.split(':')
try:
# Is the first part on OID?
_oid = univ.ObjectIdentifier(parts[0])
# It is; construct a V2 template
# pylint: disable=too-many-function-args
return MSCSTemplateV2.__new__(MSCSTemplateV2, s)
except pyasn1.error.PyAsn1Error:
# It is not an OID; treat as a template name
# pylint: disable=too-many-function-args
return MSCSTemplateV1.__new__(MSCSTemplateV1, s)
def __getstate__(self):
return self.unparsed_input
def __setstate__(self, state):
# explicitly call __init__ method to initialise object
self.__init__(state)
class MSCSTemplate(ExternalCAProfile):
"""
An Microsoft AD-CS Template specifier.
Subclasses MUST set ext_oid.
Subclass constructors MUST set asn1obj.
"""
valid_for = set([ExternalCAType.MS_CS.value])
ext_oid = None # extension OID, as a Python str
asn1obj = None # unencoded extension data
def get_ext_data(self):
"""Return DER-encoded extension data."""
return encoder.encode(self.asn1obj)
class MSCSTemplateV1(MSCSTemplate):
"""
A v1 template specifier, per
https://msdn.microsoft.com/en-us/library/cc250011.aspx.
::
CertificateTemplateName ::= SEQUENCE {
Name UTF8String
}
But note that a bare BMPString is used in practice.
"""
ext_oid = "1.3.6.1.4.1.311.20.2"
def __init__(self, s):
super(MSCSTemplateV1, self).__init__(s)
parts = s.split(':')
if len(parts) > 1:
raise ValueError(
"Cannot specify certificate template version when using name.")
self.asn1obj = char.BMPString(str(parts[0]))
class MSCSTemplateV2(MSCSTemplate):
"""
A v2 template specifier, per
https://msdn.microsoft.com/en-us/library/windows/desktop/aa378274(v=vs.85).aspx
::
CertificateTemplate ::= SEQUENCE {
templateID EncodedObjectID,
templateMajorVersion TemplateVersion,
templateMinorVersion TemplateVersion OPTIONAL
}
TemplateVersion ::= INTEGER (0..4294967295)
"""
ext_oid = "1.3.6.1.4.1.311.21.7"
@staticmethod
def check_version_in_range(desc, n):
if n < 0 or n >= 2**32:
raise ValueError(
"Template {} version must be in range 0..4294967295"
.format(desc))
def __init__(self, s):
super(MSCSTemplateV2, self).__init__(s)
parts = s.split(':')
obj = CertificateTemplateV2()
if len(parts) < 2 or len(parts) > 3:
raise ValueError(
"Incorrect template specification; required format is: "
"<oid>:<majorVersion>[:<minorVersion>]")
try:
obj['templateID'] = univ.ObjectIdentifier(parts[0])
major = int(parts[1])
self.check_version_in_range("major", major)
obj['templateMajorVersion'] = major
if len(parts) > 2:
minor = int(parts[2])
self.check_version_in_range("minor", minor)
obj['templateMinorVersion'] = int(parts[2])
except pyasn1.error.PyAsn1Error:
raise ValueError("Could not parse certificate template specifier.")
self.asn1obj = obj
class CertificateTemplateV2(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('templateID', univ.ObjectIdentifier()),
namedtype.NamedType('templateMajorVersion', univ.Integer()),
namedtype.OptionalNamedType('templateMinorVersion', univ.Integer())
)