From 1daf319a19f902d7c7bef37af065cac81be9189e Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Wed, 22 Oct 2008 17:54:04 -0400 Subject: Implement the host commands In order for this to work against a v1 database the update host.update needs to be applied --- ipalib/config.py | 1 + ipalib/plugins/b_xmlrpc.py | 2 +- ipalib/plugins/f_host.py | 271 +++++++++++++++++++++++++++++++++++++++++++++ ipalib/plugins/f_passwd.py | 12 +- ipalib/util.py | 9 ++ 5 files changed, 284 insertions(+), 11 deletions(-) create mode 100644 ipalib/plugins/f_host.py (limited to 'ipalib') diff --git a/ipalib/config.py b/ipalib/config.py index e1b12f1ee..ebd602b91 100644 --- a/ipalib/config.py +++ b/ipalib/config.py @@ -138,6 +138,7 @@ def set_default_env(env): container_user = EnvProp(basestring, 'cn=users,cn=accounts'), container_group = EnvProp(basestring, 'cn=groups,cn=accounts'), container_service = EnvProp(basestring, 'cn=services,cn=accounts'), + container_host = EnvProp(basestring, 'cn=computers,cn=accounts'), domain = LazyProp(basestring, get_domain), interactive = EnvProp(bool, True), query_dns = EnvProp(bool, True), diff --git a/ipalib/plugins/b_xmlrpc.py b/ipalib/plugins/b_xmlrpc.py index 9fe5b133a..572a75116 100644 --- a/ipalib/plugins/b_xmlrpc.py +++ b/ipalib/plugins/b_xmlrpc.py @@ -69,7 +69,7 @@ class xmlrpc(Backend): print "%s: %s" % (code, faultString) else: print "%s: %s" % (code, getattr(err,'__doc__','')) - return {} + return api.register(xmlrpc) diff --git a/ipalib/plugins/f_host.py b/ipalib/plugins/f_host.py new file mode 100644 index 000000000..da2815480 --- /dev/null +++ b/ipalib/plugins/f_host.py @@ -0,0 +1,271 @@ +# Authors: +# Rob Crittenden +# +# 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; version 2 only +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" +Frontend plugins for host/machine Identity. +""" + +from ipalib import frontend +from ipalib import crud +from ipalib import util +from ipalib.frontend import Param +from ipalib import api +from ipalib import errors +from ipalib import ipa_types + + +def get_host(hostname): + """ + Try to get the hostname as fully-qualified first, then fall back to + just a host name search. + """ + ldap = api.Backend.ldap + + # Strip off trailing dot + if hostname.endswith('.'): + hostname = hostname[:-1] + try: + dn = ldap.find_entry_dn("cn", hostname, "ipaHost") + except errors.NotFound: + dn = ldap.find_entry_dn("serverhostname", hostname, "ipaHost") + return dn + +def validate_host(cn): + """ + Require at least one dot in the hostname (to support localhost.localdomain) + """ + dots = len(cn.split('.')) + if dots < 2: + return 'Fully-qualified hostname required' + return None + + +class host(frontend.Object): + """ + Host object. + """ + takes_params = ( + Param('cn', + cli_name='hostname', + primary_key=True, + normalize=lambda value: value.lower(), + rules=(validate_host,) + ), + Param('description?', + doc='Description of the host', + ), + Param('localityname?', + cli_name='locality', + doc='Locality of this host (Baltimore, MD)', + ), + Param('nshostlocation?', + cli_name='location', + doc='Location of this host (e.g. Lab 2)', + ), + Param('nshardwareplatform?', + cli_name='platform', + doc='Hardware platform of this host (e.g. Lenovo T61)', + ), + Param('nsosversion?', + cli_name='os', + doc='Operating System and version on this host (e.g. Fedora 9)', + ), + Param('userpassword?', + cli_name='password', + doc='Set a password to be used in bulk enrollment', + ), + ) +api.register(host) + + +class host_add(crud.Add): + 'Add a new host.' + def execute(self, hostname, **kw): + """ + Execute the host-add operation. + + The dn should not be passed as a keyword argument as it is constructed + by this method. + + Returns the entry as it will be created in LDAP. + + :param hostname: The name of the host being added. + :param kw: Keyword arguments for the other LDAP attributes. + """ + assert 'cn' not in kw + assert 'dn' not in kw + ldap = self.api.Backend.ldap + + kw['cn'] = hostname + kw['serverhostname'] = hostname.split('.',1)[0] + kw['dn'] = ldap.make_host_dn(hostname) + kw['krbPrincipalName'] = "host/%s@%s" % (hostname, self.api.env.realm) + + # FIXME: do a DNS lookup to ensure host exists + + current = util.get_current_principal() + if not current: + raise errors.NotFound('Unable to determine current user') + kw['enrolledBy'] = ldap.find_entry_dn("krbPrincipalName", current, "person") + + # Get our configuration + config = ldap.get_ipa_config() + + # some required objectclasses + # FIXME: add this attribute to cn=ipaconfig + #kw['objectClass'] = config.get('ipahostobjectclasses') + kw['objectClass'] = ['nsHost', 'krbPrincipalAux', 'ipaHost'] + + return ldap.create(**kw) + def output_for_cli(self, ret): + """ + Output result of this command to command line interface. + """ + if ret: + print "Host added" + +api.register(host_add) + + +class host_del(crud.Del): + 'Delete an existing host.' + def execute(self, hostname, **kw): + """Delete a host. + + hostname is the name of the host to delete + + :param hostname: The name of the host being removed. + :param kw: Not used. + """ + ldap = self.api.Backend.ldap + dn = get_host(hostname) + return ldap.delete(dn) + def output_for_cli(self, ret): + """ + Output result of this command to command line interface. + """ + if ret: + print "Host deleted" + +api.register(host_del) + + +class host_mod(crud.Mod): + 'Edit an existing host.' + def execute(self, hostname, **kw): + """ + Execute the host-mod operation. + + The dn should not be passed as a keyword argument as it is constructed + by this method. + + Returns the entry + + :param hostname: The name of the host to retrieve. + :param kw: Keyword arguments for the other LDAP attributes. + """ + assert 'cn' not in kw + assert 'dn' not in kw + ldap = self.api.Backend.ldap + dn = get_host(hostname) + return ldap.update(dn, **kw) + + def output_for_cli(self, ret): + """ + Output result of this command to command line interface. + """ + if ret: + print "Host updated" + +api.register(host_mod) + + +class host_find(crud.Find): + 'Search the hosts.' + def get_args(self): + """ + Override Find.get_args() so we can exclude the validation rules + """ + yield self.obj.primary_key.__clone__(rules=tuple()) + def execute(self, term, **kw): + ldap = self.api.Backend.ldap + + # Pull the list of searchable attributes out of the configuration. + #config = ldap.get_ipa_config() + # FIXME: add this attribute to cn=ipaconfig + #search_fields_conf_str = config.get('ipahostsearchfields') + #search_fields = search_fields_conf_str.split(",") + search_fields = ['cn','serverhostname','description','localityname','nshostlocation','nshardwareplatform','nsosversion'] + + for s in search_fields: + kw[s] = term + + # Can't use ldap.get_object_type() since cn is also used for group dns + kw['objectclass'] = "ipaHost" + return ldap.search(**kw) + def output_for_cli(self, hosts): + if not hosts: + return + counter = hosts[0] + hosts = hosts[1:] + if counter == 0: + print "No entries found" + return + elif counter == -1: + print "These results are truncated." + print "Please refine your search and try again." + + for h in hosts: + for a in h.keys(): + print "%s: %s" % (a, h[a]) +api.register(host_find) + + +class host_show(crud.Get): + 'Examine an existing host.' + takes_options = ( + Param('all?', type=ipa_types.Bool(), doc='Display all host attributes'), + ) + def execute(self, hostname, **kw): + """ + Execute the host-show operation. + + The dn should not be passed as a keyword argument as it is constructed + by this method. + + Returns the entry + + :param hostname: The login name of the host to retrieve. + :param kw: "all" set to True = return all attributes + """ + ldap = self.api.Backend.ldap + dn = get_host(hostname) + # FIXME: should kw contain the list of attributes to display? + if kw.get('all', False): + return ldap.retrieve(dn) + else: + value = ldap.retrieve(dn, ['cn','description','localityname','nshostlocation','nshardwareplatform','nsosversion']) + del value['dn'] + return value + def output_for_cli(self, host): + if host: + for a in host.keys(): + print "%s: %s" % (a, host[a]) + +api.register(host_show) diff --git a/ipalib/plugins/f_passwd.py b/ipalib/plugins/f_passwd.py index b1f907322..f70eacac8 100644 --- a/ipalib/plugins/f_passwd.py +++ b/ipalib/plugins/f_passwd.py @@ -26,15 +26,7 @@ from ipalib.frontend import Param from ipalib import api from ipalib import errors from ipalib import ipa_types -import krbV - -def get_current_principal(): - try: - return krbV.default_context().default_ccache().principal().name - except krbV.Krb5Error: - #TODO: do a kinit - print "Unable to get kerberos principal" - return None +from ipalib import util class passwd(frontend.Command): 'Edit existing password policy.' @@ -42,7 +34,7 @@ class passwd(frontend.Command): Param('principal', cli_name='user', primary_key=True, - default_from=get_current_principal, + default_from=util.get_current_principal, ), ) def execute(self, principal, **kw): diff --git a/ipalib/util.py b/ipalib/util.py index b60bfc8aa..184c6d7c4 100644 --- a/ipalib/util.py +++ b/ipalib/util.py @@ -20,6 +20,7 @@ """ Various utility functions. """ +import krbV def xmlrpc_marshal(*args, **kw): """ @@ -39,3 +40,11 @@ def xmlrpc_unmarshal(*params): else: kw = {} return (params[1:], kw) + +def get_current_principal(): + try: + return krbV.default_context().default_ccache().principal().name + except krbV.Krb5Error: + #TODO: do a kinit + print "Unable to get kerberos principal" + return None -- cgit