Imported Debian patch 4.0.5-6~numeezy
This commit is contained in:
committed by
Mario Fetka
parent
c44de33144
commit
10dfc9587b
@@ -17,8 +17,6 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import string
|
||||
import tempfile
|
||||
import subprocess
|
||||
@@ -27,41 +25,32 @@ import os, sys, traceback
|
||||
import copy
|
||||
import stat
|
||||
import shutil
|
||||
import urllib2
|
||||
import socket
|
||||
import struct
|
||||
from types import *
|
||||
import re
|
||||
import xmlrpclib
|
||||
import datetime
|
||||
import netaddr
|
||||
import time
|
||||
import gssapi
|
||||
import krbV
|
||||
import pwd
|
||||
import grp
|
||||
from contextlib import contextmanager
|
||||
import locale
|
||||
import collections
|
||||
|
||||
from dns import resolver, rdatatype, reversename
|
||||
from dns import resolver, rdatatype
|
||||
from dns.exception import DNSException
|
||||
import six
|
||||
from six.moves import input
|
||||
from six.moves import urllib
|
||||
|
||||
from ipapython.ipa_log_manager import *
|
||||
from ipapython import ipavalidate
|
||||
from ipapython import config
|
||||
from ipaplatform.paths import paths
|
||||
from ipapython.dn import DN
|
||||
from ipapython.dnsutil import DNSName
|
||||
|
||||
SHARE_DIR = paths.USR_SHARE_IPA_DIR
|
||||
PLUGINS_SHARE_DIR = paths.IPA_PLUGINS
|
||||
|
||||
GEN_PWD_LEN = 12
|
||||
|
||||
# Having this in krb_utils would cause circular import
|
||||
KRB5_KDC_UNREACH = 2529639068 # Cannot contact any KDC for requested realm
|
||||
KRB5KDC_ERR_SVC_UNAVAILABLE = 2529638941 # A service is not available that is
|
||||
# required to process the request
|
||||
IPA_BASEDN_INFO = 'ipa v2.0'
|
||||
|
||||
try:
|
||||
from subprocess import CalledProcessError
|
||||
@@ -121,7 +110,7 @@ class CheckedIPAddress(netaddr.IPAddress):
|
||||
# netaddr.IPAddress doesn't handle zone indices in textual
|
||||
# IPv6 addresses. Try removing zone index and parse the
|
||||
# address again.
|
||||
if not isinstance(addr, six.string_types):
|
||||
if not isinstance(addr, basestring):
|
||||
raise
|
||||
addr, sep, foo = addr.partition('%')
|
||||
if sep != '%':
|
||||
@@ -155,10 +144,8 @@ class CheckedIPAddress(netaddr.IPAddress):
|
||||
elif addr.version == 6:
|
||||
family = 'inet6'
|
||||
|
||||
result = run(
|
||||
[paths.IP, '-family', family, '-oneline', 'address', 'show'],
|
||||
capture_output=True)
|
||||
lines = result.output.split('\n')
|
||||
ipresult = run([paths.IP, '-family', family, '-oneline', 'address', 'show'])
|
||||
lines = ipresult[0].split('\n')
|
||||
for line in lines:
|
||||
fields = line.split()
|
||||
if len(fields) < 4:
|
||||
@@ -242,6 +229,7 @@ def template_file(infilename, vars):
|
||||
with open(infilename) as f:
|
||||
return template_str(f.read(), vars)
|
||||
|
||||
|
||||
def copy_template_file(infilename, outfilename, vars):
|
||||
"""Copy a file, performing template substitutions"""
|
||||
txt = template_file(infilename, vars)
|
||||
@@ -250,42 +238,20 @@ def copy_template_file(infilename, outfilename, vars):
|
||||
|
||||
|
||||
def write_tmp_file(txt):
|
||||
fd = tempfile.NamedTemporaryFile('w+')
|
||||
fd = tempfile.NamedTemporaryFile()
|
||||
fd.write(txt)
|
||||
fd.flush()
|
||||
|
||||
return fd
|
||||
|
||||
def shell_quote(string):
|
||||
if isinstance(string, str):
|
||||
return "'" + string.replace("'", "'\\''") + "'"
|
||||
else:
|
||||
return b"'" + string.replace(b"'", b"'\\''") + b"'"
|
||||
return "'" + string.replace("'", "'\\''") + "'"
|
||||
|
||||
|
||||
if six.PY3:
|
||||
def _log_arg(s):
|
||||
"""Convert string or bytes to a string suitable for logging"""
|
||||
if isinstance(s, bytes):
|
||||
return s.decode(locale.getpreferredencoding(),
|
||||
errors='replace')
|
||||
else:
|
||||
return s
|
||||
else:
|
||||
_log_arg = str
|
||||
|
||||
|
||||
class _RunResult(collections.namedtuple('_RunResult',
|
||||
'output error_output returncode')):
|
||||
"""Result of ipautil.run"""
|
||||
|
||||
|
||||
def run(args, stdin=None, raiseonerr=True, nolog=(), env=None,
|
||||
capture_output=False, skip_output=False, cwd=None,
|
||||
runas=None, timeout=None, suplementary_groups=[],
|
||||
capture_error=False, encoding=None, redirect_output=False):
|
||||
def run(args, stdin=None, raiseonerr=True,
|
||||
nolog=(), env=None, capture_output=True, skip_output=False, cwd=None,
|
||||
runas=None):
|
||||
"""
|
||||
Execute an external command.
|
||||
Execute a command and return stdin, stdout and the process return code.
|
||||
|
||||
:param args: List of arguments for the command
|
||||
:param stdin: Optional input to the command
|
||||
@@ -306,67 +272,24 @@ def run(args, stdin=None, raiseonerr=True, nolog=(), env=None,
|
||||
If a value isn't found in the list it is silently ignored.
|
||||
:param env: Dictionary of environment variables passed to the command.
|
||||
When None, current environment is copied
|
||||
:param capture_output: Capture stdout
|
||||
:param skip_output: Redirect the output to /dev/null and do not log it
|
||||
:param capture_output: Capture stderr and stdout
|
||||
:param skip_output: Redirect the output to /dev/null and do not capture it
|
||||
:param cwd: Current working directory
|
||||
:param runas: Name of a user that the command should be run as. The spawned
|
||||
:param runas: Name of a user that the command shold be run as. The spawned
|
||||
process will have both real and effective UID and GID set.
|
||||
:param timeout: Timeout if the command hasn't returned within the specified
|
||||
number of seconds.
|
||||
:param suplementary_groups: List of group names that will be used as
|
||||
suplementary groups for subporcess.
|
||||
The option runas must be specified together with this option.
|
||||
:param capture_error: Capture stderr
|
||||
:param encoding: For Python 3, the encoding to use for output,
|
||||
error_output, and (if it's not bytes) stdin.
|
||||
If None, the current encoding according to locale is used.
|
||||
:param redirect_output: Redirect (error) output to standard (error) output.
|
||||
|
||||
:return: An object with these attributes:
|
||||
|
||||
`returncode`: The process' exit status
|
||||
|
||||
`output` and `error_output`: captured output, as strings. Under
|
||||
Python 3, these are encoded with the given `encoding`.
|
||||
None unless `capture_output` or `capture_error`, respectively, are
|
||||
given
|
||||
|
||||
`raw_output`, `raw_error_output`: captured output, as bytes.
|
||||
|
||||
`output_log` and `error_log`: The captured output, as strings, with any
|
||||
unencodable characters discarded. These should only be used
|
||||
for logging or error messages.
|
||||
|
||||
If skip_output is given, all output-related attributes on the result
|
||||
(that is, all except `returncode`) are None.
|
||||
|
||||
For backwards compatibility, the return value can also be used as a
|
||||
(output, error_output, returncode) triple.
|
||||
"""
|
||||
assert isinstance(suplementary_groups, list)
|
||||
p_in = None
|
||||
p_out = None
|
||||
p_err = None
|
||||
|
||||
if isinstance(nolog, six.string_types):
|
||||
if isinstance(nolog, basestring):
|
||||
# We expect a tuple (or list, or other iterable) of nolog strings.
|
||||
# Passing just a single string is bad: strings are iterable, so this
|
||||
# Passing just a single string is bad: strings are also, so this
|
||||
# would result in every individual character of that string being
|
||||
# replaced by XXXXXXXX.
|
||||
# This is a sanity check to prevent that.
|
||||
raise ValueError('nolog must be a tuple of strings.')
|
||||
|
||||
if skip_output and (capture_output or capture_error):
|
||||
raise ValueError('skip_output is incompatible with '
|
||||
'capture_output or capture_error')
|
||||
|
||||
if redirect_output and (capture_output or capture_error):
|
||||
raise ValueError('redirect_output is incompatible with '
|
||||
'capture_output or capture_error')
|
||||
|
||||
if skip_output and redirect_output:
|
||||
raise ValueError('skip_output is incompatible with redirect_output')
|
||||
|
||||
if env is None:
|
||||
# copy default env
|
||||
env = copy.deepcopy(os.environ)
|
||||
@@ -375,53 +298,29 @@ def run(args, stdin=None, raiseonerr=True, nolog=(), env=None,
|
||||
p_in = subprocess.PIPE
|
||||
if skip_output:
|
||||
p_out = p_err = open(paths.DEV_NULL, 'w')
|
||||
elif redirect_output:
|
||||
p_out = sys.stdout
|
||||
p_err = sys.stderr
|
||||
else:
|
||||
elif capture_output:
|
||||
p_out = subprocess.PIPE
|
||||
p_err = subprocess.PIPE
|
||||
|
||||
if encoding is None:
|
||||
encoding = locale.getpreferredencoding()
|
||||
|
||||
if six.PY3 and isinstance(stdin, str):
|
||||
stdin = stdin.encode(encoding)
|
||||
|
||||
if timeout:
|
||||
# If a timeout was provided, use the timeout command
|
||||
# to execute the requested command.
|
||||
args[0:0] = [paths.BIN_TIMEOUT, str(timeout)]
|
||||
|
||||
arg_string = nolog_replace(' '.join(_log_arg(a) for a in args), nolog)
|
||||
arg_string = nolog_replace(' '.join(shell_quote(a) for a in args), nolog)
|
||||
root_logger.debug('Starting external process')
|
||||
root_logger.debug('args=%s' % arg_string)
|
||||
|
||||
preexec_fn = None
|
||||
if runas is not None:
|
||||
pent = pwd.getpwnam(runas)
|
||||
|
||||
suplementary_gids = [
|
||||
grp.getgrnam(group).gr_gid for group in suplementary_groups
|
||||
]
|
||||
|
||||
root_logger.debug('runas=%s (UID %d, GID %s)', runas,
|
||||
pent.pw_uid, pent.pw_gid)
|
||||
if suplementary_groups:
|
||||
for group, gid in zip(suplementary_groups, suplementary_gids):
|
||||
root_logger.debug('suplementary_group=%s (GID %d)', group, gid)
|
||||
|
||||
preexec_fn = lambda: (
|
||||
os.setgroups(suplementary_gids),
|
||||
os.setregid(pent.pw_gid, pent.pw_gid),
|
||||
os.setreuid(pent.pw_uid, pent.pw_uid),
|
||||
)
|
||||
preexec_fn = lambda: (os.setregid(pent.pw_gid, pent.pw_gid),
|
||||
os.setreuid(pent.pw_uid, pent.pw_uid))
|
||||
|
||||
try:
|
||||
p = subprocess.Popen(args, stdin=p_in, stdout=p_out, stderr=p_err,
|
||||
close_fds=True, env=env, cwd=cwd,
|
||||
preexec_fn=preexec_fn)
|
||||
stdout, stderr = p.communicate(stdin)
|
||||
stdout,stderr = p.communicate(stdin)
|
||||
stdout,stderr = str(stdout), str(stderr) # Make pylint happy
|
||||
except KeyboardInterrupt:
|
||||
root_logger.debug('Process interrupted')
|
||||
p.wait()
|
||||
@@ -433,66 +332,29 @@ def run(args, stdin=None, raiseonerr=True, nolog=(), env=None,
|
||||
if skip_output:
|
||||
p_out.close() # pylint: disable=E1103
|
||||
|
||||
if timeout and p.returncode == 124:
|
||||
root_logger.debug('Process did not complete before timeout')
|
||||
|
||||
root_logger.debug('Process finished, return code=%s', p.returncode)
|
||||
|
||||
# The command and its output may include passwords that we don't want
|
||||
# to log. Replace those.
|
||||
if skip_output or redirect_output:
|
||||
output_log = None
|
||||
error_log = None
|
||||
else:
|
||||
if six.PY3:
|
||||
output_log = stdout.decode(locale.getpreferredencoding(),
|
||||
errors='replace')
|
||||
else:
|
||||
output_log = stdout
|
||||
if six.PY3:
|
||||
error_log = stderr.decode(locale.getpreferredencoding(),
|
||||
errors='replace')
|
||||
else:
|
||||
error_log = stderr
|
||||
output_log = nolog_replace(output_log, nolog)
|
||||
root_logger.debug('stdout=%s' % output_log)
|
||||
error_log = nolog_replace(error_log, nolog)
|
||||
root_logger.debug('stderr=%s' % error_log)
|
||||
|
||||
if capture_output:
|
||||
if six.PY2:
|
||||
output = stdout
|
||||
else:
|
||||
output = stdout.decode(encoding)
|
||||
else:
|
||||
output = None
|
||||
|
||||
if capture_error:
|
||||
if six.PY2:
|
||||
error_output = stderr
|
||||
else:
|
||||
error_output = stderr.decode(encoding)
|
||||
else:
|
||||
error_output = None
|
||||
if capture_output and not skip_output:
|
||||
stdout = nolog_replace(stdout, nolog)
|
||||
stderr = nolog_replace(stderr, nolog)
|
||||
root_logger.debug('stdout=%s' % stdout)
|
||||
root_logger.debug('stderr=%s' % stderr)
|
||||
|
||||
if p.returncode != 0 and raiseonerr:
|
||||
raise CalledProcessError(p.returncode, arg_string, str(output))
|
||||
raise CalledProcessError(p.returncode, arg_string, stdout)
|
||||
|
||||
result = _RunResult(output, error_output, p.returncode)
|
||||
result.raw_output = stdout
|
||||
result.raw_error_output = stderr
|
||||
result.output_log = output_log
|
||||
result.error_log = error_log
|
||||
return result
|
||||
return (stdout, stderr, p.returncode)
|
||||
|
||||
|
||||
def nolog_replace(string, nolog):
|
||||
"""Replace occurences of strings given in `nolog` with XXXXXXXX"""
|
||||
for value in nolog:
|
||||
if not isinstance(value, six.string_types):
|
||||
if not isinstance(value, basestring):
|
||||
continue
|
||||
|
||||
quoted = urllib.parse.quote(value)
|
||||
quoted = urllib2.quote(value)
|
||||
shquoted = shell_quote(value)
|
||||
for nolog_value in (shquoted, value, quoted):
|
||||
string = string.replace(nolog_value, 'XXXXXXXX')
|
||||
@@ -528,18 +390,18 @@ def backup_file(fname):
|
||||
if file_exists(fname):
|
||||
os.rename(fname, fname + ".orig")
|
||||
|
||||
def _ensure_nonempty_string(string, message):
|
||||
if not isinstance(string, str) or not string:
|
||||
raise ValueError(message)
|
||||
|
||||
# uses gpg to compress and encrypt a file
|
||||
def encrypt_file(source, dest, password, workdir = None):
|
||||
_ensure_nonempty_string(source, 'Missing Source File')
|
||||
if type(source) is not StringType or not len(source):
|
||||
raise ValueError('Missing Source File')
|
||||
#stat it so that we get back an exception if it does no t exist
|
||||
os.stat(source)
|
||||
|
||||
_ensure_nonempty_string(dest, 'Missing Destination File')
|
||||
_ensure_nonempty_string(password, 'Missing Password')
|
||||
if type(dest) is not StringType or not len(dest):
|
||||
raise ValueError('Missing Destination File')
|
||||
|
||||
if type(password) is not StringType or not len(password):
|
||||
raise ValueError('Missing Password')
|
||||
|
||||
#create a tempdir so that we can clean up with easily
|
||||
tempdir = tempfile.mkdtemp('', 'ipa-', workdir)
|
||||
@@ -560,12 +422,16 @@ def encrypt_file(source, dest, password, workdir = None):
|
||||
|
||||
|
||||
def decrypt_file(source, dest, password, workdir = None):
|
||||
_ensure_nonempty_string(source, 'Missing Source File')
|
||||
if type(source) is not StringType or not len(source):
|
||||
raise ValueError('Missing Source File')
|
||||
#stat it so that we get back an exception if it does no t exist
|
||||
os.stat(source)
|
||||
|
||||
_ensure_nonempty_string(dest, 'Missing Destination File')
|
||||
_ensure_nonempty_string(password, 'Missing Password')
|
||||
if type(dest) is not StringType or not len(dest):
|
||||
raise ValueError('Missing Destination File')
|
||||
|
||||
if type(password) is not StringType or not len(password):
|
||||
raise ValueError('Missing Password')
|
||||
|
||||
#create a tempdir so that we can clean up with easily
|
||||
tempdir = tempfile.mkdtemp('', 'ipa-', workdir)
|
||||
@@ -645,15 +511,14 @@ class CIDict(dict):
|
||||
for key in keys():
|
||||
self.__setitem__(key, new[key], seen)
|
||||
seen = set()
|
||||
for key, value in kwargs.items():
|
||||
for key, value in kwargs.iteritems():
|
||||
self.__setitem__(key, value, seen)
|
||||
|
||||
def __contains__(self, key):
|
||||
return super(CIDict, self).__contains__(key.lower())
|
||||
|
||||
if six.PY2:
|
||||
def has_key(self, key):
|
||||
return super(CIDict, self).has_key(key.lower())
|
||||
def has_key(self, key):
|
||||
return super(CIDict, self).has_key(key.lower())
|
||||
|
||||
def get(self, key, failobj=None):
|
||||
try:
|
||||
@@ -662,38 +527,29 @@ class CIDict(dict):
|
||||
return failobj
|
||||
|
||||
def __iter__(self):
|
||||
return six.itervalues(self._keys)
|
||||
return self._keys.itervalues()
|
||||
|
||||
def keys(self):
|
||||
if six.PY2:
|
||||
return list(self.iterkeys())
|
||||
else:
|
||||
return self.iterkeys()
|
||||
return list(self.iterkeys())
|
||||
|
||||
def items(self):
|
||||
if six.PY2:
|
||||
return list(self.iteritems())
|
||||
else:
|
||||
return self.iteritems()
|
||||
return list(self.iteritems())
|
||||
|
||||
def values(self):
|
||||
if six.PY2:
|
||||
return list(self.itervalues())
|
||||
else:
|
||||
return self.itervalues()
|
||||
return list(self.itervalues())
|
||||
|
||||
def copy(self):
|
||||
"""Returns a shallow copy of this CIDict"""
|
||||
return CIDict(list(self.items()))
|
||||
return CIDict(self.items())
|
||||
|
||||
def iteritems(self):
|
||||
return ((k, self[k]) for k in six.itervalues(self._keys))
|
||||
return ((k, self[k]) for k in self._keys.itervalues())
|
||||
|
||||
def iterkeys(self):
|
||||
return six.itervalues(self._keys)
|
||||
return self._keys.itervalues()
|
||||
|
||||
def itervalues(self):
|
||||
return (v for k, v in six.iteritems(self))
|
||||
return (v for k, v in self.iteritems())
|
||||
|
||||
def setdefault(self, key, value=None):
|
||||
try:
|
||||
@@ -863,57 +719,60 @@ def ipa_generate_password(characters=None,pwd_len=None):
|
||||
def user_input(prompt, default = None, allow_empty = True):
|
||||
if default == None:
|
||||
while True:
|
||||
try:
|
||||
ret = input("%s: " % prompt)
|
||||
if allow_empty or ret.strip():
|
||||
return ret.strip()
|
||||
except EOFError:
|
||||
if allow_empty:
|
||||
return ''
|
||||
raise RuntimeError("Failed to get user input")
|
||||
ret = raw_input("%s: " % prompt)
|
||||
if allow_empty or ret.strip():
|
||||
return ret
|
||||
|
||||
if isinstance(default, six.string_types):
|
||||
if isinstance(default, basestring):
|
||||
while True:
|
||||
try:
|
||||
ret = input("%s [%s]: " % (prompt, default))
|
||||
if not ret and (allow_empty or default):
|
||||
return default
|
||||
elif ret.strip():
|
||||
return ret.strip()
|
||||
except EOFError:
|
||||
ret = raw_input("%s [%s]: " % (prompt, default))
|
||||
if not ret and (allow_empty or default):
|
||||
return default
|
||||
|
||||
elif ret.strip():
|
||||
return ret
|
||||
if isinstance(default, bool):
|
||||
choice = "yes" if default else "no"
|
||||
if default:
|
||||
choice = "yes"
|
||||
else:
|
||||
choice = "no"
|
||||
while True:
|
||||
try:
|
||||
ret = input("%s [%s]: " % (prompt, choice))
|
||||
ret = ret.strip()
|
||||
if not ret:
|
||||
return default
|
||||
elif ret.lower()[0] == "y":
|
||||
return True
|
||||
elif ret.lower()[0] == "n":
|
||||
return False
|
||||
except EOFError:
|
||||
ret = raw_input("%s [%s]: " % (prompt, choice))
|
||||
if not ret:
|
||||
return default
|
||||
|
||||
elif ret.lower()[0] == "y":
|
||||
return True
|
||||
elif ret.lower()[0] == "n":
|
||||
return False
|
||||
if isinstance(default, int):
|
||||
while True:
|
||||
try:
|
||||
ret = input("%s [%s]: " % (prompt, default))
|
||||
ret = ret.strip()
|
||||
ret = raw_input("%s [%s]: " % (prompt, default))
|
||||
if not ret:
|
||||
return default
|
||||
ret = int(ret)
|
||||
except ValueError:
|
||||
pass
|
||||
except EOFError:
|
||||
return default
|
||||
else:
|
||||
return ret
|
||||
|
||||
|
||||
def get_gsserror(e):
|
||||
"""
|
||||
A GSSError exception looks differently in python 2.4 than it does
|
||||
in python 2.5. Deal with it.
|
||||
"""
|
||||
|
||||
try:
|
||||
major = e[0]
|
||||
minor = e[1]
|
||||
except:
|
||||
major = e[0][0]
|
||||
minor = e[0][1]
|
||||
|
||||
return (major, minor)
|
||||
|
||||
|
||||
|
||||
def host_port_open(host, port, socket_type=socket.SOCK_STREAM, socket_timeout=None):
|
||||
for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket_type):
|
||||
af, socktype, proto, canonname, sa = res
|
||||
@@ -934,7 +793,7 @@ def host_port_open(host, port, socket_type=socket.SOCK_STREAM, socket_timeout=No
|
||||
s.recv(512)
|
||||
|
||||
return True
|
||||
except socket.error as e:
|
||||
except socket.error, e:
|
||||
pass
|
||||
finally:
|
||||
if s:
|
||||
@@ -955,14 +814,14 @@ def bind_port_responder(port, socket_type=socket.SOCK_STREAM, socket_timeout=Non
|
||||
try:
|
||||
addr_infos = socket.getaddrinfo(host, port, family, socket_type, 0,
|
||||
socket.AI_PASSIVE)
|
||||
except socket.error as e:
|
||||
except socket.error, e:
|
||||
last_socket_error = e
|
||||
continue
|
||||
for res in addr_infos:
|
||||
af, socktype, proto, canonname, sa = res
|
||||
try:
|
||||
s = socket.socket(af, socktype, proto)
|
||||
except socket.error as e:
|
||||
except socket.error, e:
|
||||
last_socket_error = e
|
||||
s = None
|
||||
continue
|
||||
@@ -1001,7 +860,7 @@ def bind_port_responder(port, socket_type=socket.SOCK_STREAM, socket_timeout=Non
|
||||
# Timeout is expectable as it was requested by caller, raise
|
||||
# the exception back to him
|
||||
raise
|
||||
except socket.error as e:
|
||||
except socket.error, e:
|
||||
last_socket_error = e
|
||||
s.close()
|
||||
s = None
|
||||
@@ -1013,6 +872,16 @@ def bind_port_responder(port, socket_type=socket.SOCK_STREAM, socket_timeout=Non
|
||||
if s is None and last_socket_error is not None:
|
||||
raise last_socket_error # pylint: disable=E0702
|
||||
|
||||
def is_host_resolvable(fqdn):
|
||||
for rdtype in (rdatatype.A, rdatatype.AAAA):
|
||||
try:
|
||||
resolver.query(fqdn, rdtype)
|
||||
except DNSException:
|
||||
continue
|
||||
else:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def host_exists(host):
|
||||
"""
|
||||
@@ -1027,112 +896,45 @@ def host_exists(host):
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def reverse_record_exists(ip_address):
|
||||
def get_ipa_basedn(conn):
|
||||
"""
|
||||
Checks if IP address have some reverse record somewhere.
|
||||
Does not care where it points.
|
||||
Get base DN of IPA suffix in given LDAP server.
|
||||
|
||||
Returns True/False
|
||||
None is returned if the suffix is not found
|
||||
|
||||
:param conn: Bound LDAPClient that will be used for searching
|
||||
"""
|
||||
reverse = reversename.from_address(str(ip_address))
|
||||
try:
|
||||
resolver.query(reverse, "PTR")
|
||||
except DNSException:
|
||||
# really don't care what exception, PTR is simply unresolvable
|
||||
return False
|
||||
return True
|
||||
entry = conn.get_entry(
|
||||
DN(), attrs_list=['defaultnamingcontext', 'namingcontexts'])
|
||||
|
||||
# FIXME: import ipalib here to prevent import loops
|
||||
from ipalib import errors
|
||||
|
||||
def check_zone_overlap(zone, raise_on_error=True):
|
||||
root_logger.info("Checking DNS domain %s, please wait ..." % zone)
|
||||
if not isinstance(zone, DNSName):
|
||||
zone = DNSName(zone).make_absolute()
|
||||
|
||||
# automatic empty zones always exist so checking them is pointless,
|
||||
# do not report them to avoid meaningless error messages
|
||||
if is_auto_empty_zone(zone):
|
||||
return
|
||||
|
||||
try:
|
||||
containing_zone = resolver.zone_for_name(zone)
|
||||
except DNSException as e:
|
||||
msg = ("DNS check for domain %s failed: %s." % (zone, e))
|
||||
if raise_on_error:
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
root_logger.warning(msg)
|
||||
return
|
||||
|
||||
if containing_zone == zone:
|
||||
contexts = entry['namingcontexts']
|
||||
if 'defaultnamingcontext' in entry:
|
||||
# If there is a defaultNamingContext examine that one first
|
||||
default = entry.single_value['defaultnamingcontext']
|
||||
if default in contexts:
|
||||
contexts.remove(default)
|
||||
contexts.insert(0, default)
|
||||
for context in contexts:
|
||||
root_logger.debug("Check if naming context '%s' is for IPA" % context)
|
||||
try:
|
||||
ns = [ans.to_text() for ans in resolver.query(zone, 'NS')]
|
||||
except DNSException as e:
|
||||
root_logger.debug("Failed to resolve nameserver(s) for domain"
|
||||
" {0}: {1}".format(zone, e))
|
||||
ns = []
|
||||
|
||||
msg = u"DNS zone {0} already exists in DNS".format(zone)
|
||||
if ns:
|
||||
msg += u" and is handled by server(s): {0}".format(', '.join(ns))
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
def is_auto_empty_zone(zone):
|
||||
assert isinstance(zone, DNSName)
|
||||
|
||||
automatic_empty_zones = [DNSName(aez).make_absolute() for aez in [
|
||||
# RFC 1918
|
||||
"10.IN-ADDR.ARPA", "16.172.IN-ADDR.ARPA", "17.172.IN-ADDR.ARPA",
|
||||
"18.172.IN-ADDR.ARPA", "19.172.IN-ADDR.ARPA", "20.172.IN-ADDR.ARPA",
|
||||
"21.172.IN-ADDR.ARPA", "22.172.IN-ADDR.ARPA", "23.172.IN-ADDR.ARPA",
|
||||
"24.172.IN-ADDR.ARPA", "25.172.IN-ADDR.ARPA", "26.172.IN-ADDR.ARPA",
|
||||
"27.172.IN-ADDR.ARPA", "28.172.IN-ADDR.ARPA", "29.172.IN-ADDR.ARPA",
|
||||
"30.172.IN-ADDR.ARPA", "31.172.IN-ADDR.ARPA", "168.192.IN-ADDR.ARPA",
|
||||
# RFC 6598
|
||||
"64.100.IN-ADDR.ARPA", "65.100.IN-ADDR.ARPA", "66.100.IN-ADDR.ARPA",
|
||||
"67.100.IN-ADDR.ARPA", "68.100.IN-ADDR.ARPA", "69.100.IN-ADDR.ARPA",
|
||||
"70.100.IN-ADDR.ARPA", "71.100.IN-ADDR.ARPA", "72.100.IN-ADDR.ARPA",
|
||||
"73.100.IN-ADDR.ARPA", "74.100.IN-ADDR.ARPA", "75.100.IN-ADDR.ARPA",
|
||||
"76.100.IN-ADDR.ARPA", "77.100.IN-ADDR.ARPA", "78.100.IN-ADDR.ARPA",
|
||||
"79.100.IN-ADDR.ARPA", "80.100.IN-ADDR.ARPA", "81.100.IN-ADDR.ARPA",
|
||||
"82.100.IN-ADDR.ARPA", "83.100.IN-ADDR.ARPA", "84.100.IN-ADDR.ARPA",
|
||||
"85.100.IN-ADDR.ARPA", "86.100.IN-ADDR.ARPA", "87.100.IN-ADDR.ARPA",
|
||||
"88.100.IN-ADDR.ARPA", "89.100.IN-ADDR.ARPA", "90.100.IN-ADDR.ARPA",
|
||||
"91.100.IN-ADDR.ARPA", "92.100.IN-ADDR.ARPA", "93.100.IN-ADDR.ARPA",
|
||||
"94.100.IN-ADDR.ARPA", "95.100.IN-ADDR.ARPA", "96.100.IN-ADDR.ARPA",
|
||||
"97.100.IN-ADDR.ARPA", "98.100.IN-ADDR.ARPA", "99.100.IN-ADDR.ARPA",
|
||||
"100.100.IN-ADDR.ARPA", "101.100.IN-ADDR.ARPA",
|
||||
"102.100.IN-ADDR.ARPA", "103.100.IN-ADDR.ARPA",
|
||||
"104.100.IN-ADDR.ARPA", "105.100.IN-ADDR.ARPA",
|
||||
"106.100.IN-ADDR.ARPA", "107.100.IN-ADDR.ARPA",
|
||||
"108.100.IN-ADDR.ARPA", "109.100.IN-ADDR.ARPA",
|
||||
"110.100.IN-ADDR.ARPA", "111.100.IN-ADDR.ARPA",
|
||||
"112.100.IN-ADDR.ARPA", "113.100.IN-ADDR.ARPA",
|
||||
"114.100.IN-ADDR.ARPA", "115.100.IN-ADDR.ARPA",
|
||||
"116.100.IN-ADDR.ARPA", "117.100.IN-ADDR.ARPA",
|
||||
"118.100.IN-ADDR.ARPA", "119.100.IN-ADDR.ARPA",
|
||||
"120.100.IN-ADDR.ARPA", "121.100.IN-ADDR.ARPA",
|
||||
"122.100.IN-ADDR.ARPA", "123.100.IN-ADDR.ARPA",
|
||||
"124.100.IN-ADDR.ARPA", "125.100.IN-ADDR.ARPA",
|
||||
"126.100.IN-ADDR.ARPA", "127.100.IN-ADDR.ARPA",
|
||||
# RFC 5735 and RFC 5737
|
||||
"0.IN-ADDR.ARPA", "127.IN-ADDR.ARPA", "254.169.IN-ADDR.ARPA",
|
||||
"2.0.192.IN-ADDR.ARPA", "100.51.198.IN-ADDR.ARPA",
|
||||
"113.0.203.IN-ADDR.ARPA", "255.255.255.255.IN-ADDR.ARPA",
|
||||
# Local IPv6 Unicast Addresses
|
||||
"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA",
|
||||
"1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA",
|
||||
# LOCALLY ASSIGNED LOCAL ADDRESS SCOPE
|
||||
"D.F.IP6.ARPA", "8.E.F.IP6.ARPA", "9.E.F.IP6.ARPA", "A.E.F.IP6.ARPA",
|
||||
"B.E.F.IP6.ARPA",
|
||||
# Example Prefix, RFC 3849.
|
||||
"8.B.D.0.1.0.0.2.IP6.ARPA",
|
||||
# RFC 7534
|
||||
"EMPTY.AS112.ARPA",
|
||||
]]
|
||||
return zone in automatic_empty_zones
|
||||
[entry] = conn.get_entries(
|
||||
DN(context), conn.SCOPE_BASE, "(info=IPA*)")
|
||||
except errors.NotFound:
|
||||
root_logger.debug("LDAP server did not return info attribute to "
|
||||
"check for IPA version")
|
||||
continue
|
||||
info = entry.single_value['info'].lower()
|
||||
if info != IPA_BASEDN_INFO:
|
||||
root_logger.debug("Detected IPA server version (%s) did not match the client (%s)" \
|
||||
% (info, IPA_BASEDN_INFO))
|
||||
continue
|
||||
root_logger.debug("Naming context '%s' is a valid IPA context" % context)
|
||||
return DN(context)
|
||||
|
||||
return None
|
||||
|
||||
def config_replace_variables(filepath, replacevars=dict(), appendvars=dict()):
|
||||
"""
|
||||
@@ -1348,7 +1150,7 @@ def wait_for_open_ports(host, ports, timeout=0):
|
||||
if port_open:
|
||||
break
|
||||
if timeout and time.time() > op_timeout: # timeout exceeded
|
||||
raise socket.timeout("Timeout exceeded")
|
||||
raise socket.timeout()
|
||||
time.sleep(1)
|
||||
|
||||
def wait_for_open_socket(socket_name, timeout=0):
|
||||
@@ -1365,7 +1167,7 @@ def wait_for_open_socket(socket_name, timeout=0):
|
||||
s.connect(socket_name)
|
||||
s.close()
|
||||
break
|
||||
except socket.error as e:
|
||||
except socket.error, e:
|
||||
if e.errno in (2,111): # 111: Connection refused, 2: File not found
|
||||
if timeout and time.time() > op_timeout: # timeout exceeded
|
||||
raise e
|
||||
@@ -1373,76 +1175,27 @@ def wait_for_open_socket(socket_name, timeout=0):
|
||||
else:
|
||||
raise e
|
||||
|
||||
|
||||
def kinit_keytab(principal, keytab, ccache_name, config=None, attempts=1):
|
||||
def kinit_hostprincipal(keytab, ccachedir, principal):
|
||||
"""
|
||||
Given a ccache_path, keytab file and a principal kinit as that user.
|
||||
Given a ccache directory and a principal kinit as that user.
|
||||
|
||||
The optional parameter 'attempts' specifies how many times the credential
|
||||
initialization should be attempted in case of non-responsive KDC.
|
||||
This blindly overwrites the current CCNAME so if you need to save
|
||||
it do so before calling this function.
|
||||
|
||||
Thus far this is used to kinit as the local host.
|
||||
"""
|
||||
errors_to_retry = {KRB5KDC_ERR_SVC_UNAVAILABLE,
|
||||
KRB5_KDC_UNREACH}
|
||||
root_logger.debug("Initializing principal %s using keytab %s"
|
||||
% (principal, keytab))
|
||||
root_logger.debug("using ccache %s" % ccache_name)
|
||||
for attempt in range(1, attempts + 1):
|
||||
old_config = os.environ.get('KRB5_CONFIG')
|
||||
if config is not None:
|
||||
os.environ['KRB5_CONFIG'] = config
|
||||
else:
|
||||
os.environ.pop('KRB5_CONFIG', None)
|
||||
try:
|
||||
name = gssapi.Name(principal, gssapi.NameType.kerberos_principal)
|
||||
store = {'ccache': ccache_name,
|
||||
'client_keytab': keytab}
|
||||
cred = gssapi.Credentials(name=name, store=store, usage='initiate')
|
||||
root_logger.debug("Attempt %d/%d: success"
|
||||
% (attempt, attempts))
|
||||
return cred
|
||||
except gssapi.exceptions.GSSError as e:
|
||||
if e.min_code not in errors_to_retry: # pylint: disable=no-member
|
||||
raise
|
||||
root_logger.debug("Attempt %d/%d: failed: %s"
|
||||
% (attempt, attempts, e))
|
||||
if attempt == attempts:
|
||||
root_logger.debug("Maximum number of attempts (%d) reached"
|
||||
% attempts)
|
||||
raise
|
||||
root_logger.debug("Waiting 5 seconds before next retry")
|
||||
time.sleep(5)
|
||||
finally:
|
||||
if old_config is not None:
|
||||
os.environ['KRB5_CONFIG'] = old_config
|
||||
else:
|
||||
os.environ.pop('KRB5_CONFIG', None)
|
||||
|
||||
|
||||
def kinit_password(principal, password, ccache_name, config=None,
|
||||
armor_ccache_name=None):
|
||||
"""
|
||||
perform interactive kinit as principal using password. If using FAST for
|
||||
web-based authentication, use armor_ccache_path to specify http service
|
||||
ccache.
|
||||
"""
|
||||
root_logger.debug("Initializing principal %s using password" % principal)
|
||||
args = [paths.KINIT, principal, '-c', ccache_name]
|
||||
if armor_ccache_name is not None:
|
||||
root_logger.debug("Using armor ccache %s for FAST webauth"
|
||||
% armor_ccache_name)
|
||||
args.extend(['-T', armor_ccache_name])
|
||||
|
||||
env = {'LC_ALL': 'C'}
|
||||
if config is not None:
|
||||
env['KRB5_CONFIG'] = config
|
||||
|
||||
# this workaround enables us to capture stderr and put it
|
||||
# into the raised exception in case of unsuccessful authentication
|
||||
result = run(args, stdin=password, env=env, raiseonerr=False,
|
||||
capture_error=True)
|
||||
if result.returncode:
|
||||
raise RuntimeError(result.error_output)
|
||||
|
||||
try:
|
||||
ccache_file = 'FILE:%s/ccache' % ccachedir
|
||||
krbcontext = krbV.default_context()
|
||||
ktab = krbV.Keytab(name=keytab, context=krbcontext)
|
||||
princ = krbV.Principal(name=principal, context=krbcontext)
|
||||
os.environ['KRB5CCNAME'] = ccache_file
|
||||
ccache = krbV.CCache(name=ccache_file, context=krbcontext, primary_principal=princ)
|
||||
ccache.init(princ)
|
||||
ccache.init_creds_keytab(keytab=ktab, principal=princ)
|
||||
return ccache_file
|
||||
except krbV.Krb5Error, e:
|
||||
raise StandardError('Error initializing principal %s in %s: %s' % (principal, keytab, str(e)))
|
||||
|
||||
def dn_attribute_property(private_name):
|
||||
'''
|
||||
@@ -1483,77 +1236,5 @@ def restore_hostname(statestore):
|
||||
if old_hostname is not None and old_hostname != system_hostname:
|
||||
try:
|
||||
run([paths.BIN_HOSTNAME, old_hostname])
|
||||
except CalledProcessError as e:
|
||||
print("Failed to set this machine hostname back to %s: %s" % (old_hostname, str(e)), file=sys.stderr)
|
||||
|
||||
def posixify(string):
|
||||
"""
|
||||
Convert a string to a more strict alpha-numeric representation.
|
||||
|
||||
- Alpha-numeric, underscore, dot and dash characters are accepted
|
||||
- Space is converted to underscore
|
||||
- Other characters are omitted
|
||||
- Leading dash is stripped
|
||||
|
||||
Note: This mapping is not one-to-one and may map different input to the
|
||||
same result. When using posixify, make sure the you do not map two different
|
||||
entities to one unintentionally.
|
||||
"""
|
||||
|
||||
def valid_char(char):
|
||||
return char.isalnum() or char in ('_', '.', '-')
|
||||
|
||||
# First replace space characters
|
||||
replaced = string.replace(' ','_')
|
||||
omitted = ''.join(filter(valid_char, replaced))
|
||||
|
||||
# Leading dash is not allowed
|
||||
return omitted.lstrip('-')
|
||||
|
||||
@contextmanager
|
||||
def private_ccache(path=None):
|
||||
|
||||
if path is None:
|
||||
dir_path = tempfile.mkdtemp(prefix='krbcc')
|
||||
path = os.path.join(dir_path, 'ccache')
|
||||
else:
|
||||
dir_path = None
|
||||
|
||||
original_value = os.environ.get('KRB5CCNAME', None)
|
||||
|
||||
os.environ['KRB5CCNAME'] = path
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if original_value is not None:
|
||||
os.environ['KRB5CCNAME'] = original_value
|
||||
else:
|
||||
os.environ.pop('KRB5CCNAME', None)
|
||||
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
if dir_path is not None:
|
||||
try:
|
||||
os.rmdir(dir_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
if six.PY2:
|
||||
def fsdecode(value):
|
||||
"""
|
||||
Decode argument using the file system encoding, as returned by
|
||||
`sys.getfilesystemencoding()`.
|
||||
"""
|
||||
if isinstance(value, six.binary_type):
|
||||
return value.decode(sys.getfilesystemencoding())
|
||||
elif isinstance(value, six.text_type):
|
||||
return value
|
||||
else:
|
||||
raise TypeError("expect {0} or {1}, not {2}".format(
|
||||
six.binary_type.__name__,
|
||||
six.text_type.__name__,
|
||||
type(value).__name__))
|
||||
else:
|
||||
fsdecode = os.fsdecode #pylint: disable=no-member
|
||||
except CalledProcessError, e:
|
||||
print >>sys.stderr, "Failed to set this machine hostname back to %s: %s" % (old_hostname, str(e))
|
||||
|
||||
Reference in New Issue
Block a user