Imported Upstream version 4.6.2

This commit is contained in:
Mario Fetka
2021-07-25 07:32:41 +02:00
commit 8ff3be4216
1788 changed files with 1900965 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
#
"""
Sub-package containing all client plugins.
"""

View File

@@ -0,0 +1,35 @@
# Authors:
# Jr Aquino <jr.aquino@citrix.com>
#
# Copyright (C) 2011 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/>.
from ipaclient.frontend import MethodOverride
from ipalib.frontend import Str
from ipalib.plugable import Registry
from ipalib.text import _
register = Registry()
@register(override=True, no_fail=True)
class automember_add_condition(MethodOverride):
has_output_params = (
Str('failed',
label=_('Failed to add'),
flags=['suppress_empty'],
),
)

View File

@@ -0,0 +1,303 @@
# Authors:
# Rob Crittenden <rcritten@redhat.com>
# Pavel Zuna <pzuna@redhat.com>
#
# Copyright (C) 2008 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/>.
import os
import six
from ipaclient.frontend import MethodOverride
from ipalib import api, errors
from ipalib import Flag, Str
from ipalib.frontend import Command, Method, Object
from ipalib.plugable import Registry
from ipalib.util import classproperty
from ipalib import _
from ipapython.dn import DN
if six.PY3:
unicode = str
register = Registry()
DEFAULT_MAPS = (u'auto.direct', )
DEFAULT_KEYS = (u'/-', )
@register(no_fail=True)
class _fake_automountlocation(Object):
name = 'automountlocation'
@register(no_fail=True)
class _fake_automountlocation_show(Method):
name = 'automountlocation_show'
NO_CLI = True
@register(override=True, no_fail=True)
class automountlocation_tofiles(MethodOverride):
@classmethod
def __NO_CLI_getter(cls):
return (api.Command.get_plugin('automountlocation_show') is
_fake_automountlocation_show)
NO_CLI = classproperty(__NO_CLI_getter)
@property
def api_version(self):
return self.api.Command.automountlocation_show.api_version
def output_for_cli(self, textui, result, *keys, **options):
maps = result['result']['maps']
keys = result['result']['keys']
orphanmaps = result['result']['orphanmaps']
orphankeys = result['result']['orphankeys']
textui.print_plain('/etc/auto.master:')
for m in maps:
if m['automountinformation'][0].startswith('-'):
textui.print_plain(
'%s\t%s' % (
m['automountkey'][0], m['automountinformation'][0]
)
)
else:
textui.print_plain(
'%s\t/etc/%s' % (
m['automountkey'][0], m['automountinformation'][0]
)
)
for m in maps:
if m['automountinformation'][0].startswith('-'):
continue
info = m['automountinformation'][0]
textui.print_plain('---------------------------')
textui.print_plain('/etc/%s:' % info)
for k in keys[info]:
textui.print_plain(
'%s\t%s' % (
k['automountkey'][0], k['automountinformation'][0]
)
)
textui.print_plain('')
textui.print_plain(_('maps not connected to /etc/auto.master:'))
for m in orphanmaps:
textui.print_plain('---------------------------')
textui.print_plain('/etc/%s:' % m['automountmapname'])
for k in orphankeys:
if len(k) == 0: continue
dn = DN(k[0]['dn'])
if dn['automountmapname'] == m['automountmapname'][0]:
textui.print_plain(
'%s\t%s' % (
k[0]['automountkey'][0], k[0]['automountinformation'][0]
)
)
@register()
class automountlocation_import(Command):
__doc__ = _('Import automount files for a specific location.')
takes_args = (
Str('masterfile',
label=_('Master file'),
doc=_('Automount master file.'),
),
)
takes_options = (
Flag('continue?',
cli_name='continue',
doc=_('Continuous operation mode. Errors are reported but the process continues.'),
),
)
def get_args(self):
for arg in self.api.Command.automountlocation_show.args():
yield arg
for arg in super(automountlocation_import, self).get_args():
yield arg
def __read_mapfile(self, filename):
try:
fp = open(filename, 'r')
map = fp.readlines()
fp.close()
except IOError as e:
if e.errno == 2:
raise errors.NotFound(
reason=_('File %(file)s not found') % {'file': filename}
)
else:
raise
return map
def forward(self, *args, **options):
"""
The basic idea is to read the master file and create all the maps
we need, then read each map file and add all the keys for the map.
"""
self.api.Command['automountlocation_show'](args[0])
result = {'maps':[], 'keys':[], 'skipped':[], 'duplicatekeys':[], 'duplicatemaps':[]}
maps = {}
master = self.__read_mapfile(args[1])
for m in master:
if m.startswith('#'):
continue
m = m.rstrip()
if m.startswith('+'):
result['skipped'].append([m,args[1]])
continue
if len(m) == 0:
continue
am = m.split(None)
if len(am) < 2:
continue
if am[1].startswith('/'):
mapfile = am[1].replace('"','')
am[1] = os.path.basename(am[1])
maps[am[1]] = mapfile
# Add a new key to the auto.master map for the new map file
try:
api.Command['automountkey_add'](
args[0],
u'auto.master',
automountkey=unicode(am[0]),
automountinformation=unicode(' '.join(am[1:])))
result['keys'].append([am[0], u'auto.master'])
except errors.DuplicateEntry:
if unicode(am[0]) in DEFAULT_KEYS:
# ignore conflict when the key was pre-created by the framework
pass
elif options.get('continue', False):
result['duplicatekeys'].append(am[0])
else:
raise errors.DuplicateEntry(
message=_('key %(key)s already exists') % dict(
key=am[0]))
# Add the new map
if not am[1].startswith('-'):
try:
api.Command['automountmap_add'](args[0], unicode(am[1]))
result['maps'].append(am[1])
except errors.DuplicateEntry:
if unicode(am[1]) in DEFAULT_MAPS:
# ignore conflict when the map was pre-created by the framework
pass
elif options.get('continue', False):
result['duplicatemaps'].append(am[0])
else:
raise errors.DuplicateEntry(
message=_('map %(map)s already exists') % dict(
map=am[1]))
# Now iterate over the map files and add the keys. To handle
# continuation lines I'll make a pass through it to skip comments
# etc and also to combine lines.
for m in maps:
map = self.__read_mapfile(maps[m])
lines = []
cont = ''
for x in map:
if x.startswith('#'):
continue
x = x.rstrip()
if x.startswith('+'):
result['skipped'].append([m, maps[m]])
continue
if len(x) == 0:
continue
if x.endswith("\\"):
cont = cont + x[:-1] + ' '
else:
lines.append(cont + x)
cont=''
for x in lines:
am = x.split(None)
key = unicode(am[0].replace('"',''))
try:
api.Command['automountkey_add'](
args[0],
unicode(m),
automountkey=key,
automountinformation=unicode(' '.join(am[1:])))
result['keys'].append([key,m])
except errors.DuplicateEntry as e:
if options.get('continue', False):
result['duplicatekeys'].append(am[0])
else:
raise e
return dict(result=result)
def output_for_cli(self, textui, result, *keys, **options):
maps = result['result']['maps']
keys = result['result']['keys']
duplicatemaps = result['result']['duplicatemaps']
duplicatekeys = result['result']['duplicatekeys']
skipped = result['result']['skipped']
textui.print_plain(_('Imported maps:'))
for m in maps:
textui.print_plain(
_('Added %(map)s') % dict(map=m)
)
textui.print_plain('')
textui.print_plain(_('Imported keys:'))
for k in keys:
textui.print_plain(
_('Added %(src)s to %(dst)s') % dict(
src=k[0], dst=k[1]
)
)
textui.print_plain('')
if len(skipped) > 0:
textui.print_plain(_('Ignored keys:'))
for k in skipped:
textui.print_plain(
_('Ignored %(src)s to %(dst)s') % dict(
src=k[0], dst=k[1]
)
)
if options.get('continue', False) and len(duplicatemaps) > 0:
textui.print_plain('')
textui.print_plain(_('Duplicate maps skipped:'))
for m in duplicatemaps:
textui.print_plain(
_('Skipped %(map)s') % dict(map=m)
)
if options.get('continue', False) and len(duplicatekeys) > 0:
textui.print_plain('')
textui.print_plain(_('Duplicate keys skipped:'))
for k in duplicatekeys:
textui.print_plain(
_('Skipped %(key)s') % dict(key=k)
)

56
ipaclient/plugins/ca.py Normal file
View File

@@ -0,0 +1,56 @@
#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
#
from ipaclient.frontend import MethodOverride
from ipalib import errors, util, x509, Str
from ipalib.plugable import Registry
from ipalib.text import _
register = Registry()
class WithCertOutArgs(MethodOverride):
takes_options = (
Str(
'certificate_out?',
doc=_('Write certificate (chain if --chain used) to file'),
include='cli',
cli_metavar='FILE',
),
)
def forward(self, *keys, **options):
filename = None
if 'certificate_out' in options:
filename = options.pop('certificate_out')
try:
util.check_writable_file(filename)
except errors.FileError as e:
raise errors.ValidationError(name='certificate-out',
error=str(e))
result = super(WithCertOutArgs, self).forward(*keys, **options)
if filename:
if options.get('chain', False):
certs = (x509.load_der_x509_certificate(c)
for c in result['result']['certificate_chain'])
else:
certs = [
x509.load_der_x509_certificate(
result['result']['certificate'])
]
x509.write_certificate_list(certs, filename)
return result
@register(override=True, no_fail=True)
class ca_add(WithCertOutArgs):
pass
@register(override=True, no_fail=True)
class ca_show(WithCertOutArgs):
pass

212
ipaclient/plugins/cert.py Normal file
View File

@@ -0,0 +1,212 @@
# Authors:
# Andrew Wnuk <awnuk@redhat.com>
# Jason Gerard DeRose <jderose@redhat.com>
# John Dennis <jdennis@redhat.com>
#
# Copyright (C) 2009 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/>.
import base64
import six
from ipaclient import csrgen
from ipaclient.frontend import MethodOverride
from ipalib import errors
from ipalib import x509
from ipalib import util
from ipalib.parameters import File, Flag, Str
from ipalib.plugable import Registry
from ipalib.text import _
if six.PY3:
unicode = str
register = Registry()
class CertRetrieveOverride(MethodOverride):
takes_options = (
Str(
'certificate_out?',
doc=_('Write certificate (chain if --chain used) to file'),
include='cli',
cli_metavar='FILE',
),
)
def forward(self, *args, **options):
if 'certificate_out' in options:
certificate_out = options.pop('certificate_out')
try:
util.check_writable_file(certificate_out)
except errors.FileError as e:
raise errors.ValidationError(name='certificate-out',
error=str(e))
else:
certificate_out = None
result = super(CertRetrieveOverride, self).forward(*args, **options)
if certificate_out is not None:
if options.get('chain', False):
certs = result['result']['certificate_chain']
else:
certs = [result['result']['certificate']]
certs = (x509.load_der_x509_certificate(base64.b64decode(cert))
for cert in certs)
x509.write_certificate_list(certs, certificate_out)
return result
@register(override=True, no_fail=True)
class cert_request(CertRetrieveOverride):
takes_options = CertRetrieveOverride.takes_options + (
Str(
'database?',
label=_('Path to NSS database'),
doc=_('Path to NSS database to use for private key'),
),
Str(
'private_key?',
label=_('Path to private key file'),
doc=_('Path to PEM file containing a private key'),
),
Str(
'password_file?',
label=_(
'File containing a password for the private key or database'),
),
Str(
'csr_profile_id?',
label=_('Name of CSR generation profile (if not the same as'
' profile_id)'),
),
)
def get_args(self):
for arg in super(cert_request, self).get_args():
if arg.name == 'csr':
arg = arg.clone_retype(arg.name, File, required=False)
yield arg
def forward(self, csr=None, **options):
database = options.pop('database', None)
private_key = options.pop('private_key', None)
csr_profile_id = options.pop('csr_profile_id', None)
password_file = options.pop('password_file', None)
if csr is None:
if database:
adaptor = csrgen.NSSAdaptor(database, password_file)
elif private_key:
adaptor = csrgen.OpenSSLAdaptor(private_key, password_file)
else:
raise errors.InvocationError(
message=u"One of 'database' or 'private_key' is required")
pubkey_info = adaptor.get_subject_public_key_info()
pubkey_info_b64 = base64.b64encode(pubkey_info)
# If csr_profile_id is passed, that takes precedence.
# Otherwise, use profile_id. If neither are passed, the default
# in cert_get_requestdata will be used.
profile_id = csr_profile_id
if profile_id is None:
profile_id = options.get('profile_id')
response = self.api.Command.cert_get_requestdata(
profile_id=profile_id,
principal=options.get('principal'),
public_key_info=pubkey_info_b64)
req_info_b64 = response['result']['request_info']
req_info = base64.b64decode(req_info_b64)
csr = adaptor.sign_csr(req_info)
if not csr:
raise errors.CertificateOperationError(
error=(_('Generated CSR was empty')))
else:
if database is not None or private_key is not None:
raise errors.MutuallyExclusiveError(reason=_(
"Options 'database' and 'private_key' are not compatible"
" with 'csr'"))
return super(cert_request, self).forward(csr, **options)
@register(override=True, no_fail=True)
class cert_show(CertRetrieveOverride):
def get_options(self):
for option in super(cert_show, self).get_options():
if option.name == 'out':
# skip server-defined --out
continue
if option.name == 'certificate_out':
# add --out as a deprecated alias of --certificate-out
option = option.clone_rename(
'out',
cli_name='certificate_out',
deprecated_cli_aliases={'out'},
)
yield option
def forward(self, *args, **options):
try:
options['certificate_out'] = options.pop('out')
except KeyError:
pass
return super(cert_show, self).forward(*args, **options)
@register(override=True, no_fail=True)
class cert_remove_hold(MethodOverride):
has_output_params = (
Flag('unrevoked',
label=_('Unrevoked'),
),
Str('error_string',
label=_('Error'),
),
)
@register(override=True, no_fail=True)
class cert_find(MethodOverride):
takes_options = (
File(
'file?',
label=_("Input filename"),
doc=_('File to load the certificate from.'),
include='cli',
),
)
def forward(self, *args, **options):
if self.api.env.context == 'cli':
if 'certificate' in options and 'file' in options:
raise errors.MutuallyExclusiveError(
reason=_("cannot specify both raw certificate and file"))
if 'certificate' not in options and 'file' in options:
options['certificate'] = x509.load_unknown_x509_certificate(
options.pop('file'))
return super(cert_find, self).forward(*args, **options)

View File

@@ -0,0 +1,49 @@
#
# Copyright (C) 2017 FreeIPA Contributors see COPYING for license
#
from ipaclient.frontend import MethodOverride
from ipalib import errors, x509
from ipalib.parameters import File
from ipalib.plugable import Registry
from ipalib.text import _
register = Registry()
@register(override=True, no_fail=True)
class certmap_match(MethodOverride):
takes_args = (
File(
'file?',
label=_("Input file"),
doc=_("File to load the certificate from"),
include='cli',
),
)
def get_args(self):
for arg in super(certmap_match, self).get_args():
if arg.name != 'certificate' or self.api.env.context != 'cli':
yield arg
def get_options(self):
for arg in super(certmap_match, self).get_args():
if arg.name == 'certificate' and self.api.env.context == 'cli':
yield arg.clone(required=False)
for option in super(certmap_match, self).get_options():
yield option
def forward(self, *args, **options):
if self.api.env.context == 'cli':
if args and 'certificate' in options:
raise errors.MutuallyExclusiveError(
reason=_("cannot specify both raw certificate and file"))
if args:
args = [x509.load_unknown_x509_certificate(args[0])]
elif 'certificate' in options:
args = [options.pop('certificate')]
else:
args = []
return super(certmap_match, self).forward(*args, **options)

View File

@@ -0,0 +1,47 @@
#
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
#
from ipaclient.frontend import MethodOverride
from ipalib import util
from ipalib.parameters import File
from ipalib.plugable import Registry
from ipalib.text import _
register = Registry()
@register(override=True, no_fail=True)
class certprofile_show(MethodOverride):
def forward(self, *keys, **options):
if 'out' in options:
util.check_writable_file(options['out'])
result = super(certprofile_show, self).forward(*keys, **options)
if 'out' in options and 'config' in result['result']:
with open(options['out'], 'wb') as f:
f.write(result['result'].pop('config'))
result['summary'] = (
_("Profile configuration stored in file '%(file)s'")
% dict(file=options['out'])
)
return result
@register(override=True, no_fail=True)
class certprofile_import(MethodOverride):
def get_options(self):
for option in super(certprofile_import, self).get_options():
if option.name == 'file':
option = option.clone_retype(option.name, File)
yield option
@register(override=True, no_fail=True)
class certprofile_mod(MethodOverride):
def get_options(self):
for option in super(certprofile_mod, self).get_options():
if option.name == 'file':
option = option.clone_retype(option.name, File)
yield option

130
ipaclient/plugins/csrgen.py Normal file
View File

@@ -0,0 +1,130 @@
#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
#
import base64
import six
from ipalib import api
from ipalib import errors
from ipalib import output
from ipalib import util
from ipalib.frontend import Local, Str
from ipalib.parameters import Bytes, Principal
from ipalib.plugable import Registry
from ipalib.text import _
from ipapython import dogtag
try:
import jinja2 # pylint: disable=unused-import
except ImportError:
raise errors.SkipPluginModule(reason=_("jinja2 is not installed."))
else:
from ipaclient import csrgen
from ipaclient import csrgen_ffi
if six.PY3:
unicode = str
register = Registry()
__doc__ = _("""
Commands to build certificate requests automatically
""")
@register()
class cert_get_requestdata(Local):
__doc__ = _('Gather data for a certificate signing request.')
NO_CLI = True
takes_options = (
Principal(
'principal',
label=_('Principal'),
doc=_('Principal for this certificate (e.g.'
' HTTP/test.example.com)'),
),
Str(
'profile_id?',
label=_('Profile ID'),
doc=_('CSR Generation Profile to use'),
),
Bytes(
'public_key_info',
label=_('Subject Public Key Info'),
doc=_('DER-encoded SubjectPublicKeyInfo structure'),
),
Str(
'out?',
doc=_('Write CertificationRequestInfo to file'),
),
)
has_output = (
output.Output(
'result',
type=dict,
doc=_('Dictionary mapping variable name to value'),
),
)
has_output_params = (
Str(
'request_info',
label=_('CertificationRequestInfo structure'),
)
)
def execute(self, *args, **options):
if 'out' in options:
util.check_writable_file(options['out'])
principal = options.get('principal')
profile_id = options.get('profile_id')
if profile_id is None:
profile_id = dogtag.DEFAULT_PROFILE
public_key_info = options.get('public_key_info')
public_key_info = base64.b64decode(public_key_info)
if self.api.env.in_server:
backend = self.api.Backend.ldap2
else:
backend = self.api.Backend.rpcclient
if not backend.isconnected():
backend.connect()
try:
if principal.is_host:
principal_obj = api.Command.host_show(
principal.hostname, all=True)
elif principal.is_service:
principal_obj = api.Command.service_show(
unicode(principal), all=True)
elif principal.is_user:
principal_obj = api.Command.user_show(
principal.username, all=True)
except errors.NotFound:
raise errors.NotFound(
reason=_("The principal for this request doesn't exist."))
principal_obj = principal_obj['result']
config = api.Command.config_show()['result']
generator = csrgen.CSRGenerator(csrgen.FileRuleProvider())
csr_config = generator.csr_config(principal_obj, config, profile_id)
request_info = base64.b64encode(csrgen_ffi.build_requestinfo(
csr_config.encode('utf8'), public_key_info))
result = {}
if 'out' in options:
with open(options['out'], 'wb') as f:
f.write(request_info)
else:
result = dict(request_info=request_info)
return dict(
result=result
)

507
ipaclient/plugins/dns.py Normal file
View File

@@ -0,0 +1,507 @@
# Authors:
# Martin Kosek <mkosek@redhat.com>
# Pavel Zuna <pzuna@redhat.com>
#
# Copyright (C) 2010 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/>.
from __future__ import print_function
import six
import copy
import re
from ipaclient.frontend import MethodOverride
from ipalib import errors
from ipalib.dns import (get_record_rrtype,
has_cli_options,
iterate_rrparams_by_parts,
part_name_format,
record_name_format)
from ipalib.frontend import Command
from ipalib.parameters import Bool, Str
from ipalib.plugable import Registry
from ipalib import _, ngettext
from ipalib import util
from ipapython.dnsutil import DNSName
if six.PY3:
unicode = str
register = Registry()
# most used record types, always ask for those in interactive prompt
_top_record_types = ('A', 'AAAA', )
_rev_top_record_types = ('PTR', )
_zone_top_record_types = ('NS', 'MX', 'LOC', )
def __get_part_param(rrtype, cmd, part, output_kw, default=None):
name = part_name_format % (rrtype.lower(), part.name)
label = unicode(cmd.params[name].label)
optional = not part.required
output_kw[name] = cmd.prompt_param(part,
optional=optional,
label=label)
def prompt_parts(rrtype, cmd, mod_dnsvalue=None):
mod_parts = None
if mod_dnsvalue is not None:
name = record_name_format % unicode(rrtype.lower())
mod_parts = cmd.api.Command.dnsrecord_split_parts(
name, mod_dnsvalue)['result']
user_options = {}
try:
rrobj = cmd.api.Object['dns{}record'.format(rrtype.lower())]
except KeyError:
return user_options
for part_id, part in enumerate(rrobj.params()):
name = part_name_format % (rrtype.lower(), part.name)
if name not in cmd.params:
continue
if mod_parts:
default = mod_parts[part_id]
else:
default = None
__get_part_param(rrtype, cmd, part, user_options, default)
return user_options
def prompt_missing_parts(rrtype, cmd, kw, prompt_optional=False):
user_options = {}
try:
rrobj = cmd.api.Object['dns{}record'.format(rrtype.lower())]
except KeyError:
return user_options
for part in rrobj.params():
name = part_name_format % (rrtype.lower(), part.name)
if name not in cmd.params:
continue
if name in kw:
continue
optional = not part.required
if optional and not prompt_optional:
continue
default = part.get_default(**kw)
__get_part_param(rrtype, cmd, part, user_options, default)
return user_options
class DNSZoneMethodOverride(MethodOverride):
def get_options(self):
for option in super(DNSZoneMethodOverride, self).get_options():
if option.name == 'idnsallowdynupdate':
option = option.clone_retype(option.name, Bool)
yield option
@register(override=True, no_fail=True)
class dnszone_add(DNSZoneMethodOverride):
pass
@register(override=True, no_fail=True)
class dnszone_mod(DNSZoneMethodOverride):
pass
# Support old servers without dnsrecord_split_parts
# Do not add anything new here!
@register(no_fail=True)
class dnsrecord_split_parts(Command):
NO_CLI = True
takes_args = (
Str('name'),
Str('value'),
)
def execute(self, name, value, *args, **options):
def split_exactly(count):
values = value.split()
if len(values) != count:
return None
return tuple(values)
result = ()
rrtype = get_record_rrtype(name)
if rrtype in ('A', 'AAAA', 'CNAME', 'DNAME', 'NS', 'PTR'):
result = split_exactly(1)
elif rrtype in ('AFSDB', 'KX', 'MX'):
result = split_exactly(2)
elif rrtype in ('CERT', 'DLV', 'DS', 'SRV', 'TLSA'):
result = split_exactly(4)
elif rrtype in ('NAPTR'):
result = split_exactly(6)
elif rrtype in ('A6', 'TXT'):
result = (value,)
elif rrtype == 'LOC':
regex = re.compile(
r'(?P<d1>\d{1,2}\s+)'
r'(?:(?P<m1>\d{1,2}\s+)'
r'(?P<s1>\d{1,2}(?:\.\d{1,3})?\s+)?)?'
r'(?P<dir1>[NS])\s+'
r'(?P<d2>\d{1,3}\s+)'
r'(?:(?P<m2>\d{1,2}\s+)'
r'(?P<s2>\d{1,2}(?:\.\d{1,3})?\s+)?)?'
r'(?P<dir2>[WE])\s+'
r'(?P<alt>-?\d{1,8}(?:\.\d{1,2})?)m?'
r'(?:\s+(?P<siz>\d{1,8}(?:\.\d{1,2})?)m?'
r'(?:\s+(?P<hp>\d{1,8}(?:\.\d{1,2})?)m?'
r'(?:\s+(?P<vp>\d{1,8}(?:\.\d{1,2})?)m?\s*)?)?)?$')
m = regex.match(value)
if m is not None:
result = tuple(
x.strip() if x is not None else x for x in m.groups())
elif rrtype == 'SSHFP':
values = value.split(None, 2)
if len(values) == 3:
result = tuple(values)
return dict(result=result)
@register(override=True, no_fail=True)
class dnsrecord_add(MethodOverride):
no_option_msg = 'No options to add a specific record provided.\n' \
"Command help may be consulted for all supported record types."
def interactive_prompt_callback(self, kw):
try:
has_cli_options(self, kw, self.no_option_msg)
# Some DNS records were entered, do not use full interactive help
# We should still ask user for required parts of DNS parts he is
# trying to add in the same way we do for standard LDAP parameters
#
# Do not ask for required parts when any "extra" option is used,
# it can be used to fill all required params by itself
new_kw = {}
for rrparam in iterate_rrparams_by_parts(self, kw,
skip_extra=True):
rrtype = get_record_rrtype(rrparam.name)
user_options = prompt_missing_parts(rrtype, self, kw,
prompt_optional=False)
new_kw.update(user_options)
kw.update(new_kw)
return
except errors.OptionError:
pass
try:
idnsname = DNSName(kw['idnsname'])
except Exception as e:
raise errors.ValidationError(name='idnsname', error=unicode(e))
try:
zonename = DNSName(kw['dnszoneidnsname'])
except Exception as e:
raise errors.ValidationError(name='dnszoneidnsname', error=unicode(e))
# check zone type
if idnsname.is_empty():
common_types = u', '.join(_zone_top_record_types)
elif zonename.is_reverse():
common_types = u', '.join(_rev_top_record_types)
else:
common_types = u', '.join(_top_record_types)
self.Backend.textui.print_plain(_(u'Please choose a type of DNS resource record to be added'))
self.Backend.textui.print_plain(_(u'The most common types for this type of zone are: %s\n') %\
common_types)
ok = False
while not ok:
rrtype = self.Backend.textui.prompt(_(u'DNS resource record type'))
if rrtype is None:
return
rrtype = rrtype.upper()
try:
name = record_name_format % rrtype.lower()
param = self.params[name]
if 'no_option' in param.flags:
raise ValueError()
except (KeyError, ValueError):
all_types = u', '.join(get_record_rrtype(p.name)
for p in self.params()
if (get_record_rrtype(p.name) and
'no_option' not in p.flags))
self.Backend.textui.print_plain(_(u'Invalid or unsupported type. Allowed values are: %s') % all_types)
continue
ok = True
user_options = prompt_parts(rrtype, self)
kw.update(user_options)
@register(override=True, no_fail=True)
class dnsrecord_mod(MethodOverride):
no_option_msg = 'No options to modify a specific record provided.'
def interactive_prompt_callback(self, kw):
try:
has_cli_options(self, kw, self.no_option_msg, True)
except errors.OptionError:
pass
else:
# some record type entered, skip this helper
return
# get DNS record first so that the NotFound exception is raised
# before the helper would start
dns_record = self.api.Command['dnsrecord_show'](kw['dnszoneidnsname'], kw['idnsname'])['result']
self.Backend.textui.print_plain(_("No option to modify specific record provided."))
# ask user for records to be removed
self.Backend.textui.print_plain(_(u'Current DNS record contents:\n'))
record_params = []
for attr in dns_record:
try:
param = self.params[attr]
except KeyError:
continue
rrtype = get_record_rrtype(param.name)
if not rrtype:
continue
record_params.append((param, rrtype))
rec_type_content = u', '.join(dns_record[param.name])
self.Backend.textui.print_plain(u'%s: %s' % (param.label, rec_type_content))
self.Backend.textui.print_plain(u'')
# ask what records to remove
for param, rrtype in record_params:
rec_values = list(dns_record[param.name])
for rec_value in dns_record[param.name]:
rec_values.remove(rec_value)
mod_value = self.Backend.textui.prompt_yesno(
_("Modify %(name)s '%(value)s'?") % dict(name=param.label, value=rec_value), default=False)
if mod_value is True:
user_options = prompt_parts(rrtype, self,
mod_dnsvalue=rec_value)
kw[param.name] = [rec_value]
kw.update(user_options)
if rec_values:
self.Backend.textui.print_plain(ngettext(
u'%(count)d %(type)s record skipped. Only one value per DNS record type can be modified at one time.',
u'%(count)d %(type)s records skipped. Only one value per DNS record type can be modified at one time.',
0) % dict(count=len(rec_values), type=rrtype))
break
@register(override=True, no_fail=True)
class dnsrecord_del(MethodOverride):
no_option_msg = _('Neither --del-all nor options to delete a specific record provided.\n'\
"Command help may be consulted for all supported record types.")
def interactive_prompt_callback(self, kw):
if kw.get('del_all', False):
return
try:
has_cli_options(self, kw, self.no_option_msg)
except errors.OptionError:
pass
else:
# some record type entered, skip this helper
return
# get DNS record first so that the NotFound exception is raised
# before the helper would start
dns_record = self.api.Command['dnsrecord_show'](kw['dnszoneidnsname'], kw['idnsname'])['result']
self.Backend.textui.print_plain(_("No option to delete specific record provided."))
user_del_all = self.Backend.textui.prompt_yesno(_("Delete all?"), default=False)
if user_del_all is True:
kw['del_all'] = True
return
# ask user for records to be removed
self.Backend.textui.print_plain(_(u'Current DNS record contents:\n'))
present_params = []
for attr in dns_record:
try:
param = self.params[attr]
except KeyError:
continue
if not get_record_rrtype(param.name):
continue
present_params.append(param)
rec_type_content = u', '.join(dns_record[param.name])
self.Backend.textui.print_plain(u'%s: %s' % (param.label, rec_type_content))
self.Backend.textui.print_plain(u'')
# ask what records to remove
for param in present_params:
deleted_values = []
for rec_value in dns_record[param.name]:
user_del_value = self.Backend.textui.prompt_yesno(
_("Delete %(name)s '%(value)s'?")
% dict(name=param.label, value=rec_value), default=False)
if user_del_value is True:
deleted_values.append(rec_value)
if deleted_values:
kw[param.name] = tuple(deleted_values)
@register(override=True, no_fail=True)
class dnsconfig_mod(MethodOverride):
def interactive_prompt_callback(self, kw):
# show informative message on client side
# server cannot send messages asynchronous
if kw.get('idnsforwarders', False):
self.Backend.textui.print_plain(
_("Server will check DNS forwarder(s)."))
self.Backend.textui.print_plain(
_("This may take some time, please wait ..."))
@register(override=True, no_fail=True)
class dnsforwardzone_add(MethodOverride):
def interactive_prompt_callback(self, kw):
if ('idnsforwarders' not in kw and
kw.get('idnsforwardpolicy') != u'none'):
kw['idnsforwarders'] = self.Backend.textui.prompt(
_(u'DNS forwarder'))
# show informative message on client side
# server cannot send messages asynchronous
if kw.get('idnsforwarders', False):
self.Backend.textui.print_plain(
_("Server will check DNS forwarder(s)."))
self.Backend.textui.print_plain(
_("This may take some time, please wait ..."))
@register(override=True, no_fail=True)
class dnsforwardzone_mod(MethodOverride):
def interactive_prompt_callback(self, kw):
# show informative message on client side
# server cannot send messages asynchronous
if kw.get('idnsforwarders', False):
self.Backend.textui.print_plain(
_("Server will check DNS forwarder(s)."))
self.Backend.textui.print_plain(
_("This may take some time, please wait ..."))
@register(override=True, no_fail=True)
class dns_update_system_records(MethodOverride):
record_groups = ('ipa_records', 'location_records')
takes_options = (
Str(
'out?',
include='cli',
doc=_('file to store DNS records in nsupdate format')
),
)
def _standard_output(self, textui, result, labels):
"""Print output in standard format common across the other plugins"""
for key in self.record_groups:
if result.get(key):
textui.print_indented(u'{}:'.format(labels[key]), indent=1)
for val in sorted(result[key]):
textui.print_indented(val, indent=2)
textui.print_line(u'')
def _nsupdate_output_file(self, out_f, result):
"""Store data in nsupdate format in file"""
def parse_rname_rtype(record):
"""Get rname and rtype from textual representation of record"""
l = record.split(' ', 4)
return l[0], l[3]
labels = {
p.name: unicode(p.label) for p in self.output_params()
}
already_removed = set()
for key in self.record_groups:
if result.get(key): # process only non-empty
out_f.write("; {}\n".format(labels[key])) # comment
for val in sorted(result[key]):
# delete old first
r_name_type = parse_rname_rtype(val)
if r_name_type not in already_removed:
# remove it only once
already_removed.add(r_name_type)
out_f.write("update delete {rname} {rtype}\n".format(
rname=r_name_type[0], rtype=r_name_type[1]
))
# add new
out_f.write("update add {}\n".format(val))
out_f.write("send\n\n")
def forward(self, *keys, **options):
# pop `out` before sending to server as it is only client side option
out = options.pop('out', None)
if out:
util.check_writable_file(out)
res = super(dns_update_system_records, self).forward(*keys, **options)
if out and 'result' in res:
try:
with open(out, "w") as f:
self._nsupdate_output_file(f, res['result'])
except (OSError, IOError) as e:
raise errors.FileError(reason=unicode(e))
return res
def output_for_cli(self, textui, output, *args, **options):
output_super = copy.deepcopy(output)
super_res = output_super.get('result', {})
super_res.pop('ipa_records', None)
super_res.pop('location_records', None)
super(dns_update_system_records, self).output_for_cli(
textui, output_super, *args, **options)
labels = {
p.name: unicode(p.label) for p in self.output_params()
}
result = output.get('result', {})
self._standard_output(textui, result, labels)
return int(not output['value'])

View File

@@ -0,0 +1,45 @@
# Authors:
# Pavel Zuna <pzuna@redhat.com>
#
# Copyright (C) 2009 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/>.
from ipaclient.frontend import MethodOverride
from ipalib.plugable import Registry
register = Registry()
#@register()
class hbacrule_add_accesstime(MethodOverride):
def output_for_cli(self, textui, result, cn, **options):
textui.print_name(self.name)
textui.print_dashed(
'Added access time "%s" to HBAC rule "%s"' % (
options['accesstime'], cn
)
)
#@register()
class hbacrule_remove_accesstime(MethodOverride):
def output_for_cli(self, textui, result, cn, **options):
textui.print_name(self.name)
textui.print_dashed(
'Removed access time "%s" from HBAC rule "%s"' % (
options['accesstime'], cn
)
)

View File

@@ -0,0 +1,57 @@
# Authors:
# Alexander Bokovoy <abokovoy@redhat.com>
#
# Copyright (C) 2011 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/>.
from ipaclient.frontend import CommandOverride
from ipalib.plugable import Registry
import six
if six.PY3:
unicode = str
register = Registry()
@register(override=True, no_fail=True)
class hbactest(CommandOverride):
def output_for_cli(self, textui, output, *args, **options):
"""
Command.output_for_cli() uses --all option to decide whether to print detailed output.
We use --detail to allow that, thus we need to redefine output_for_cli().
"""
# Note that we don't actually use --detail below to see if details need
# to be printed as our execute() method will return None for corresponding
# entries and None entries will be skipped.
for o in self.output:
if o == 'value':
continue
outp = self.output[o]
if 'no_display' in outp.flags:
continue
result = output[o]
if isinstance(result, (list, tuple)):
textui.print_attribute(unicode(outp.doc), result, '%s: %s', 1, True)
elif isinstance(result, unicode):
if o == 'summary':
textui.print_summary(result)
else:
textui.print_indented(result)
# Propagate integer value for result. It will give proper command line result for scripts
return int(not output['value'])

48
ipaclient/plugins/host.py Normal file
View File

@@ -0,0 +1,48 @@
# Authors:
# Rob Crittenden <rcritten@redhat.com>
# Pavel Zuna <pzuna@redhat.com>
#
# Copyright (C) 2008 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/>.
from ipaclient.frontend import MethodOverride
from ipalib import errors, util
from ipalib.plugable import Registry
from ipalib import _
from ipalib import x509
register = Registry()
@register(override=True, no_fail=True)
class host_show(MethodOverride):
def forward(self, *keys, **options):
if 'out' in options:
util.check_writable_file(options['out'])
result = super(host_show, self).forward(*keys, **options)
if 'usercertificate' in result['result']:
certs = (x509.load_der_x509_certificate(c)
for c in result['result']['usercertificate'])
x509.write_certificate_list(certs, options['out'])
result['summary'] = (
_('Certificate(s) stored in file \'%(file)s\'')
% dict(file=options['out'])
)
return result
else:
raise errors.NoCertificateError(entry=keys[-1])
else:
return super(host_show, self).forward(*keys, **options)

View File

@@ -0,0 +1,89 @@
# Authors:
# Sumit Bose <sbose@redhat.com>
#
# Copyright (C) 2012 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/>.
from ipaclient.frontend import MethodOverride
from ipalib.plugable import Registry
from ipalib import api
register = Registry()
@register(override=True, no_fail=True)
class idrange_add(MethodOverride):
def interactive_prompt_callback(self, kw):
"""
Ensure that rid-base is prompted for when dom-sid is specified.
Also ensure that secondary-rid-base is prompted for when rid-base is
specified and vice versa, in case that dom-sid was not specified.
Also ensure that rid-base and secondary-rid-base is prompted for
if ipa-adtrust-install has been run on the system.
"""
# dom-sid can be specified using dom-sid or dom-name options
# it can be also set using --setattr or --addattr, in these cases
# we will not prompt, but raise an ValidationError later
dom_sid_set = any(dom_id in kw for dom_id in
('ipanttrusteddomainname', 'ipanttrusteddomainsid'))
rid_base = kw.get('ipabaserid', None)
secondary_rid_base = kw.get('ipasecondarybaserid', None)
range_type = kw.get('iparangetype', None)
def set_from_prompt(param):
value = self.prompt_param(self.params[param])
update = {param: value}
kw.update(update)
if dom_sid_set:
# This is a trusted range
# Prompt for RID base if domain SID / name was given
if rid_base is None and range_type != u'ipa-ad-trust-posix':
set_from_prompt('ipabaserid')
else:
# This is a local range
# Find out whether ipa-adtrust-install has been ran
adtrust_is_enabled = api.Command['adtrust_is_enabled']()['result']
if adtrust_is_enabled:
# If ipa-adtrust-install has been ran, all local ranges
# require both RID base and secondary RID base
if rid_base is None:
set_from_prompt('ipabaserid')
if secondary_rid_base is None:
set_from_prompt('ipasecondarybaserid')
else:
# This is a local range on a server with no adtrust support
# Prompt for secondary RID base only if RID base was given
if rid_base is not None and secondary_rid_base is None:
set_from_prompt('ipasecondarybaserid')
# Symetrically, prompt for RID base if secondary RID base was
# given
if rid_base is None and secondary_rid_base is not None:
set_from_prompt('ipabaserid')

View File

@@ -0,0 +1,42 @@
# Authors:
# Pavel Zuna <pzuna@redhat.com>
# Adam Young <ayoung@redhat.com>
# Endi S. Dewata <edewata@redhat.com>
#
# Copyright (c) 2010 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/>.
from __future__ import print_function
import json
from ipaclient.frontend import CommandOverride
from ipalib.util import json_serialize
from ipalib.plugable import Registry
register = Registry()
@register(override=True, no_fail=True)
class json_metadata(CommandOverride):
def output_for_cli(self, textui, result, *args, **options):
print(json.dumps(result, default=json_serialize))
@register(override=True, no_fail=True)
class i18n_messages(CommandOverride):
def output_for_cli(self, textui, result, *args, **options):
print(json.dumps(result, default=json_serialize))

View File

@@ -0,0 +1,35 @@
#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
#
from ipaclient.frontend import MethodOverride
from ipalib import _
from ipalib.plugable import Registry
register = Registry()
@register(override=True, no_fail=True)
class location_show(MethodOverride):
def output_for_cli(self, textui, output, *keys, **options):
rv = super(location_show, self).output_for_cli(
textui, output, *keys, **options)
servers = output.get('servers', {})
first = True
for details in servers.values():
if first:
textui.print_indented(_("Servers details:"), indent=1)
first = False
else:
textui.print_line("")
for param in self.api.Command.server_find.output_params():
if param.name in details:
textui.print_indented(
u"{}: {}".format(
param.label, u', '.join(details[param.name])),
indent=2)
return rv

View File

@@ -0,0 +1,79 @@
# Authors:
# Pavel Zuna <pzuna@redhat.com>
#
# Copyright (C) 2009 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/>.
import six
from ipaclient.frontend import CommandOverride
from ipalib.parameters import File
from ipalib.plugable import Registry
from ipalib import _
if six.PY3:
unicode = str
register = Registry()
@register(override=True, no_fail=True)
class migrate_ds(CommandOverride):
migrate_order = ('user', 'group')
migration_disabled_msg = _('''\
Migration mode is disabled.
Use \'ipa config-mod --enable-migration=TRUE\' to enable it.''')
pwd_migration_msg = _('''\
Passwords have been migrated in pre-hashed format.
IPA is unable to generate Kerberos keys unless provided
with clear text passwords. All migrated users need to
login at https://your.domain/ipa/migration/ before they
can use their Kerberos accounts.''')
def get_options(self):
for option in super(migrate_ds, self).get_options():
if option.name == 'cacertfile':
option = option.clone_retype(option.name, File)
yield option
def output_for_cli(self, textui, result, ldapuri, **options):
textui.print_name(self.name)
if not result['enabled']:
textui.print_plain(self.migration_disabled_msg)
return 1
if not result['compat']:
textui.print_plain("The compat plug-in is enabled. This can increase the memory requirements during migration. Disable the compat plug-in with \'ipa-compat-manage disable\' or re-run this script with \'--with-compat\' option.")
return 1
any_migrated = any(result['result'].values())
textui.print_plain('Migrated:')
textui.print_entry1(
result['result'], attr_order=self.migrate_order,
one_value_per_line=False
)
for ldap_obj_name in self.migrate_order:
textui.print_plain('Failed %s:' % ldap_obj_name)
textui.print_entry1(
result['failed'][ldap_obj_name], attr_order=self.migrate_order,
one_value_per_line=True,
)
textui.print_plain('-' * len(self.name))
if not any_migrated:
textui.print_plain('No users/groups were migrated from %s' %
ldapuri)
return 1
textui.print_plain(unicode(self.pwd_migration_msg))

28
ipaclient/plugins/misc.py Normal file
View File

@@ -0,0 +1,28 @@
#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
#
from ipalib.misc import env as _env
from ipalib.misc import plugins as _plugins
from ipalib.plugable import Registry
register = Registry()
@register(override=True, no_fail=True)
class env(_env):
def output_for_cli(self, textui, output, *args, **options):
output = dict(output)
output.pop('count', None)
output.pop('total', None)
options['all'] = True
return super(env, self).output_for_cli(textui, output,
*args, **options)
@register(override=True, no_fail=True)
class plugins(_plugins):
def output_for_cli(self, textui, output, *args, **options):
options['all'] = True
return super(plugins, self).output_for_cli(textui, output,
*args, **options)

View File

@@ -0,0 +1,190 @@
# Authors:
# Nathaniel McCallum <npmccallum@redhat.com>
#
# Copyright (C) 2013 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/>.
from __future__ import print_function
import sys
from ipaclient.frontend import MethodOverride
from ipalib import api, Str, Password, _
from ipalib.messages import add_message, ResultFormattingError
from ipalib.plugable import Registry
from ipalib.frontend import Local
from ipalib.util import create_https_connection
from ipapython.dn import DN
from ipapython.version import API_VERSION
import locale
import qrcode
import six
from six import StringIO
from six.moves import urllib
if six.PY3:
unicode = str
register = Registry()
@register(override=True, no_fail=True)
class otptoken_add(MethodOverride):
def _get_qrcode(self, output, uri, version):
# Print QR code to terminal if specified
qr_output = StringIO()
qr = qrcode.QRCode()
qr.add_data(uri)
qr.make()
qr.print_ascii(out=qr_output, tty=False)
encoding = getattr(sys.stdout, 'encoding', None)
if encoding is None:
encoding = locale.getpreferredencoding(False)
try:
qr_code = qr_output.getvalue().encode(encoding)
except UnicodeError:
add_message(
version,
output,
message=ResultFormattingError(
message=_("Unable to display QR code using the configured "
"output encoding. Please use the token URI to "
"configure your OTP device")
)
)
return None
if sys.stdout.isatty():
output_width = self.api.Backend.textui.get_tty_width()
qr_code_width = len(qr_code.splitlines()[0])
if qr_code_width > output_width:
add_message(
version,
output,
message=ResultFormattingError(
message=_(
"QR code width is greater than that of the output "
"tty. Please resize your terminal.")
)
)
return qr
def output_for_cli(self, textui, output, *args, **options):
# copy-pasted from ipalib/Frontend.__do_call()
# because option handling is broken on client-side
if 'version' in options:
pass
elif self.api.env.skip_version_check:
options['version'] = u'2.0'
else:
options['version'] = API_VERSION
uri = output['result'].get('uri', None)
if uri is not None and not options.get('no_qrcode', False):
qr = self._get_qrcode(output, uri, options['version'])
else:
qr = None
rv = super(otptoken_add, self).output_for_cli(
textui, output, *args, **options)
if qr is not None:
print("\n")
qr.print_ascii(tty=sys.stdout.isatty())
print("\n")
return rv
class HTTPSHandler(urllib.request.HTTPSHandler):
"Opens SSL HTTPS connections that perform hostname validation."
def __init__(self, **kwargs):
self.__kwargs = kwargs
# Can't use super() because the parent is an old-style class.
urllib.request.HTTPSHandler.__init__(self)
def __inner(self, host, **kwargs):
tmp = self.__kwargs.copy()
tmp.update(kwargs)
return create_https_connection(host, **tmp)
def https_open(self, req):
# pylint: disable=no-member
return self.do_open(self.__inner, req)
@register()
class otptoken_sync(Local):
__doc__ = _('Synchronize an OTP token.')
header = 'X-IPA-TokenSync-Result'
takes_options = (
Str('user', label=_('User ID')),
Password('password', label=_('Password'), confirm=False),
Password('first_code', label=_('First Code'), confirm=False),
Password('second_code', label=_('Second Code'), confirm=False),
)
takes_args = (
Str('token?', label=_('Token ID')),
)
def forward(self, *args, **kwargs):
status = {'result': {self.header: 'unknown'}}
# Get the sync URI.
segments = list(urllib.parse.urlparse(self.api.env.xmlrpc_uri))
assert segments[0] == 'https' # Ensure encryption.
segments[2] = segments[2].replace('/xml', '/session/sync_token')
# urlunparse *can* take one argument
# pylint: disable=too-many-function-args
sync_uri = urllib.parse.urlunparse(segments)
# Prepare the query.
query = {k: v for k, v in kwargs.items()
if k in {x.name for x in self.takes_options}}
if args and args[0] is not None:
obj = self.api.Object.otptoken
query['token'] = DN((obj.primary_key.name, args[0]),
obj.container_dn, self.api.env.basedn)
query = urllib.parse.urlencode(query)
# Sync the token.
# pylint: disable=E1101
handler = HTTPSHandler(
cafile=api.env.tls_ca_cert,
tls_version_min=api.env.tls_version_min,
tls_version_max=api.env.tls_version_max)
rsp = urllib.request.build_opener(handler).open(sync_uri, query)
if rsp.getcode() == 200:
status['result'][self.header] = rsp.info().get(self.header, 'unknown')
rsp.close()
return status
def output_for_cli(self, textui, result, *keys, **options):
textui.print_plain({
'ok': 'Token synchronized.',
'error': 'Error contacting server!',
'invalid-credentials': 'Invalid Credentials!',
}.get(result['result'][self.header], 'Unknown Error!'))

View File

@@ -0,0 +1,192 @@
# Authors:
# Nathaniel McCallum <npmccallum@redhat.com>
#
# Copyright (C) 2014 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/>.
import os
import six
from ipalib import _, api, IntEnum
from ipalib.errors import NotFound, SkipPluginModule
from ipalib.frontend import Command, Method, Object
from ipalib.plugable import Registry
from ipalib.util import classproperty
try:
import usb.core
import yubico
except ImportError:
# python-yubico depends on pyusb
raise SkipPluginModule(reason=_("python-yubico is not installed."))
if six.PY3:
unicode = str
__doc__ = _("""
YubiKey Tokens
""") + _("""
Manage YubiKey tokens.
""") + _("""
This code is an extension to the otptoken plugin and provides support for
reading/writing YubiKey tokens directly.
""") + _("""
EXAMPLES:
""") + _("""
Add a new token:
ipa otptoken-add-yubikey --owner=jdoe --desc="My YubiKey"
""")
register = Registry()
topic = 'otp'
@register(no_fail=True)
class _fake_otptoken(Object):
name = 'otptoken'
@register(no_fail=True)
class _fake_otptoken_add(Method):
name = 'otptoken_add'
NO_CLI = True
@register()
class otptoken_add_yubikey(Command):
__doc__ = _('Add a new YubiKey OTP token.')
takes_options = (
IntEnum('slot?',
cli_name='slot',
label=_('YubiKey slot'),
values=(1, 2),
),
)
has_output_params = takes_options
@classmethod
def __NO_CLI_getter(cls):
return api.Command.get_plugin('otptoken_add') is _fake_otptoken_add
NO_CLI = classproperty(__NO_CLI_getter)
@property
def api_version(self):
return self.api.Command.otptoken_add.api_version
def get_args(self):
for arg in self.api.Command.otptoken_add.args():
yield arg
for arg in super(otptoken_add_yubikey, self).get_args():
yield arg
def get_options(self):
for option in self.api.Command.otptoken_add.options():
if option.name not in ('type',
'ipatokenvendor',
'ipatokenmodel',
'ipatokenserial',
'ipatokenotpalgorithm',
'ipatokenhotpcounter',
'ipatokenotpkey',
'ipatokentotpclockoffset',
'ipatokentotptimestep',
'no_qrcode',
'qrcode',
'version'):
yield option
for option in super(otptoken_add_yubikey, self).get_options():
yield option
def get_output_params(self):
for param in self.api.Command.otptoken_add.output_params():
yield param
for param in super(otptoken_add_yubikey, self).get_output_params():
yield param
def _iter_output(self):
return self.api.Command.otptoken_add.output()
def forward(self, *args, **kwargs):
# Open the YubiKey
try:
yk = yubico.find_yubikey()
except usb.core.USBError as e:
raise NotFound(reason="No YubiKey found: %s" % e.strerror)
except yubico.yubikey.YubiKeyError as e:
raise NotFound(reason=e.reason)
except ValueError as e:
raise NotFound(reason=str(e) + ". Please install 'libyubikey' "
"and 'libusb' packages first.")
assert yk.version_num() >= (2, 1)
# If no slot is specified, find the first free slot.
if kwargs.get('slot', None) is None:
try:
used = yk.status().valid_configs()
kwargs['slot'] = sorted({1, 2}.difference(used))[0]
except IndexError:
raise NotFound(reason=_('No free YubiKey slot!'))
# Create the key (NOTE: the length is fixed).
key = os.urandom(20)
# Write the config.
cfg = yk.init_config()
cfg.mode_oath_hotp(key, kwargs.get(
'ipatokenotpdigits',
self.get_default_of('ipatokenotpdigits')
))
cfg.extended_flag('SERIAL_API_VISIBLE', True)
yk.write_config(cfg, slot=kwargs['slot'])
# Filter the options we want to pass.
options = {k: v for k, v in kwargs.items() if k in (
'version',
'description',
'ipatokenowner',
'ipatokendisabled',
'ipatokennotbefore',
'ipatokennotafter',
'ipatokenotpdigits',
)}
# Run the command.
answer = self.Backend.rpcclient.forward('otptoken_add',
*args,
type=u'hotp',
ipatokenvendor=u'YubiCo',
ipatokenmodel=unicode(yk.model),
ipatokenserial=unicode(yk.serial()),
ipatokenotpalgorithm=u'sha1',
ipatokenhotpcounter=0,
ipatokenotpkey=key,
no_qrcode=True,
**options)
# Suppress values we don't want to return.
for k in (u'uri', u'ipatokenotpkey'):
if k in answer.get('result', {}):
del answer['result'][k]
# Return which slot was used for writing.
answer.get('result', {})['slot'] = kwargs['slot']
return answer

View File

@@ -0,0 +1,17 @@
#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
#
from ipaclient.frontend import CommandOverride
from ipalib.plugable import Registry
register = Registry()
@register(override=True, no_fail=True)
class passwd(CommandOverride):
def get_args(self):
for arg in super(passwd, self).get_args():
if arg.name == 'current_password':
arg = arg.clone(sortorder=-1)
yield arg

View File

@@ -0,0 +1,31 @@
#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
#
from ipaclient.frontend import MethodOverride
from ipalib.plugable import Registry
register = Registry()
class PermissionMethodOverride(MethodOverride):
def get_options(self):
for option in super(PermissionMethodOverride, self).get_options():
if option.name == 'ipapermright':
option = option.clone(deprecated_cli_aliases={'permissions'})
yield option
@register(override=True, no_fail=True)
class permission_add(PermissionMethodOverride):
pass
@register(override=True, no_fail=True)
class permission_mod(PermissionMethodOverride):
pass
@register(override=True, no_fail=True)
class permission_find(PermissionMethodOverride):
pass

View File

@@ -0,0 +1,53 @@
# Authors:
# Jason Gerard DeRose <jderose@redhat.com>
# Rob Crittenden <rcritten@redhat.com>
# Petr Viktorin <pviktori@redhat.com>
#
# Copyright (C) 2008-2013 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/>.
"""
RPC client plugins.
"""
from ipalib import Registry, api
register = Registry()
if 'in_server' in api.env and api.env.in_server is False:
from ipalib.rpc import xmlclient, jsonclient
register()(xmlclient)
register()(jsonclient)
# FIXME: api.register only looks at the class name, so we need to create
# trivial subclasses with the desired name.
if api.env.rpc_protocol == 'xmlrpc':
class rpcclient(xmlclient):
"""xmlclient renamed to 'rpcclient'"""
pass
register()(rpcclient)
elif api.env.rpc_protocol == 'jsonrpc':
class rpcclient(jsonclient):
"""jsonclient renamed to 'rpcclient'"""
pass
register()(rpcclient)
else:
raise ValueError('unknown rpc_protocol: %s' % api.env.rpc_protocol)

View File

@@ -0,0 +1,21 @@
#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
#
from ipaclient.frontend import MethodOverride
from ipalib import _, errors
from ipalib.plugable import Registry
register = Registry()
@register(override=True, no_fail=True)
class server_del(MethodOverride):
def interactive_prompt_callback(self, kw):
server_list = kw.get('cn')
if not server_list:
raise errors.RequirementError(name='cn')
self.api.Backend.textui.print_plain(
_("Removing %(servers)s from replication topology, "
"please wait...") % {'servers': ', '.join(server_list)})

View File

@@ -0,0 +1,50 @@
# Authors:
# Jason Gerard DeRose <jderose@redhat.com>
# Rob Crittenden <rcritten@redhat.com>
# Pavel Zuna <pzuna@redhat.com>
#
# Copyright (C) 2008 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/>.
from ipaclient.frontend import MethodOverride
from ipalib import errors
from ipalib.plugable import Registry
from ipalib import x509
from ipalib import _
from ipalib import util
register = Registry()
@register(override=True, no_fail=True)
class service_show(MethodOverride):
def forward(self, *keys, **options):
if 'out' in options:
util.check_writable_file(options['out'])
result = super(service_show, self).forward(*keys, **options)
if 'usercertificate' in result['result']:
certs = (x509.load_der_x509_certificate(c)
for c in result['result']['usercertificate'])
x509.write_certificate_list(certs, options['out'])
result['summary'] = (
_('Certificate(s) stored in file \'%(file)s\'')
% dict(file=options['out'])
)
return result
else:
raise errors.NoCertificateError(entry=keys[-1])
else:
return super(service_show, self).forward(*keys, **options)

View File

@@ -0,0 +1,57 @@
# Authors:
# Jr Aquino <jr.aquino@citrixonline.com>
#
# Copyright (C) 2010-2014 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/>.
from ipaclient.frontend import MethodOverride
from ipalib.plugable import Registry
from ipalib import _
register = Registry()
@register(override=True, no_fail=True)
class sudorule_enable(MethodOverride):
def output_for_cli(self, textui, result, cn, **options):
textui.print_dashed(_('Enabled Sudo Rule "%s"') % cn)
@register(override=True, no_fail=True)
class sudorule_disable(MethodOverride):
def output_for_cli(self, textui, result, cn, **options):
textui.print_dashed(_('Disabled Sudo Rule "%s"') % cn)
@register(override=True, no_fail=True)
class sudorule_add_option(MethodOverride):
def output_for_cli(self, textui, result, cn, **options):
textui.print_dashed(
_('Added option "%(option)s" to Sudo Rule "%(rule)s"')
% dict(option=options['ipasudoopt'], rule=cn))
super(sudorule_add_option, self).output_for_cli(textui, result, cn,
**options)
@register(override=True, no_fail=True)
class sudorule_remove_option(MethodOverride):
def output_for_cli(self, textui, result, cn, **options):
textui.print_dashed(
_('Removed option "%(option)s" from Sudo Rule "%(rule)s"')
% dict(option=options['ipasudoopt'], rule=cn))
super(sudorule_remove_option, self).output_for_cli(textui, result, cn,
**options)

View File

@@ -0,0 +1,53 @@
#
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
#
import six
from ipaclient.frontend import MethodOverride
from ipalib.plugable import Registry
from ipalib import _
if six.PY3:
unicode = str
register = Registry()
@register(override=True, no_fail=True)
class topologysuffix_verify(MethodOverride):
def output_for_cli(self, textui, output, *args, **options):
connect_errors = output['result']['connect_errors']
max_agmts_errors = output['result']['max_agmts_errors']
if not connect_errors and not max_agmts_errors:
header = _('Replication topology of suffix "%(suffix)s" '
'is in order.')
textui.print_h1(header % {'suffix': args[0]})
if connect_errors:
header = _('Replication topology of suffix "%(suffix)s" contains '
'errors.')
textui.print_h1(header % {'suffix': args[0]})
textui.print_dashed(unicode(_('Topology is disconnected')))
for err in connect_errors:
msg = _("Server %(srv)s can't contact servers: %(replicas)s")
msg = msg % {'srv': err[0], 'replicas': ', '.join(err[2])}
textui.print_indented(msg)
if max_agmts_errors:
textui.print_dashed(unicode(_('Recommended maximum number of '
'agreements per replica exceeded')))
textui.print_attribute(
unicode(_("Maximum number of agreements per replica")),
[output['result']['max_agmts']]
)
for err in max_agmts_errors:
msg = _('Server "%(srv)s" has %(n)d agreements with servers:')
msg = msg % {'srv': err[0], 'n': len(err[1])}
textui.print_indented(msg)
for replica in err[1]:
textui.print_indented(replica, 2)
return 0

View File

@@ -0,0 +1,51 @@
# Authors:
# Alexander Bokovoy <abokovoy@redhat.com>
# Martin Kosek <mkosek@redhat.com>
#
# Copyright (C) 2011 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/>.
from ipaclient.frontend import MethodOverride
from ipalib.plugable import Registry
register = Registry()
@register(override=True, no_fail=True)
class trust_add(MethodOverride):
def interactive_prompt_callback(self, kw):
"""
Also ensure that realm_admin is prompted for if --admin or
--trust-secret is not specified when 'ipa trust-add' is run on the
system.
Also ensure that realm_passwd is prompted for if --password or
--trust-secret is not specified when 'ipa trust-add' is run on the
system.
"""
trust_secret = kw.get('trust_secret')
realm_admin = kw.get('realm_admin')
realm_passwd = kw.get('realm_passwd')
if trust_secret is None:
if realm_admin is None:
kw['realm_admin'] = self.prompt_param(
self.params['realm_admin'])
if realm_passwd is None:
kw['realm_passwd'] = self.Backend.textui.prompt_password(
self.params['realm_passwd'].label, confirm=False)

81
ipaclient/plugins/user.py Normal file
View File

@@ -0,0 +1,81 @@
# Authors:
# Jason Gerard DeRose <jderose@redhat.com>
# Pavel Zuna <pzuna@redhat.com>
#
# Copyright (C) 2008 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/>.
from ipaclient.frontend import MethodOverride
from ipalib import errors
from ipalib import Flag
from ipalib import util
from ipalib.plugable import Registry
from ipalib import _
from ipalib import x509
register = Registry()
@register(override=True, no_fail=True)
class user_del(MethodOverride):
def get_options(self):
for option in super(user_del, self).get_options():
yield option
yield Flag(
'preserve?',
include='cli',
doc=_('Delete a user, keeping the entry available for future use'),
)
yield Flag(
'no_preserve?',
include='cli',
doc=_('Delete a user'),
)
def forward(self, *keys, **options):
if self.api.env.context == 'cli':
no_preserve = options.pop('no_preserve', False)
preserve = options.pop('preserve', False)
if no_preserve and preserve:
raise errors.MutuallyExclusiveError(
reason=_("preserve and no-preserve cannot be both set"))
elif no_preserve:
options['preserve'] = False
elif preserve:
options['preserve'] = True
return super(user_del, self).forward(*keys, **options)
@register(override=True, no_fail=True)
class user_show(MethodOverride):
def forward(self, *keys, **options):
if 'out' in options:
util.check_writable_file(options['out'])
result = super(user_show, self).forward(*keys, **options)
if 'usercertificate' in result['result']:
certs = (x509.load_der_x509_certificate(c)
for c in result['result']['usercertificate'])
x509.write_certificate_list(certs, options['out'])
result['summary'] = (
_('Certificate(s) stored in file \'%(file)s\'')
% dict(file=options['out'])
)
return result
else:
raise errors.NoCertificateError(entry=keys[-1])
else:
return super(user_show, self).forward(*keys, **options)

1140
ipaclient/plugins/vault.py Normal file

File diff suppressed because it is too large Load Diff