# Authors: Sumit Bose # # 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 . # from __future__ import print_function import os import errno import ldap import tempfile import uuid import string import struct import re import socket import six from ipaserver.dns_data_management import IPASystemRecords from ipaserver.install import service from ipaserver.install import installutils from ipaserver.install.bindinstance import dns_zone_exists from ipaserver.install.replication import wait_for_task from ipalib import errors, api from ipalib.util import normalize_zone from ipapython.dn import DN from ipapython import ipautil from ipapython.ipa_log_manager import root_logger import ipapython.errors import ipaclient.install.ipachangeconf from ipaplatform import services from ipaplatform.constants import constants from ipaplatform.paths import paths from ipaplatform.tasks import tasks if six.PY3: unicode = str ALLOWED_NETBIOS_CHARS = string.ascii_uppercase + string.digits + '-' UPGRADE_ERROR = """ Entry %(dn)s does not exist. This means upgrade from IPA 2.x to 3.x did not went well and required S4U2Proxy configuration was not set up properly. Please run ipa-ldap-updater manually and re-run ipa-adtrust-instal again afterwards. """ def check_inst(): for smbfile in [paths.SMBD, paths.NET]: if not os.path.exists(smbfile): print("%s was not found on this system" % smbfile) print("Please install the 'samba' packages and " \ "start the installation again") return False #TODO: Add check for needed samba4 libraries return True def ipa_smb_conf_exists(): try: conf_fd = open(paths.SMB_CONF, 'r') except IOError as err: if err.errno == errno.ENOENT: return False else: raise lines = conf_fd.readlines() conf_fd.close() for line in lines: if line.startswith('### Added by IPA Installer ###'): return True return False def check_netbios_name(name): # Empty NetBIOS name is not allowed if not name: return False # NetBIOS names may not be longer than 15 allowed characters invalid_netbios_name = any([ len(name) > 15, ''.join([c for c in name if c not in ALLOWED_NETBIOS_CHARS]) ]) return not invalid_netbios_name def make_netbios_name(s): return ''.join([c for c in s.split('.')[0].upper() \ if c in ALLOWED_NETBIOS_CHARS])[:15] class ADTRUSTInstance(service.Service): ATTR_SID = "ipaNTSecurityIdentifier" ATTR_FLAT_NAME = "ipaNTFlatName" ATTR_GUID = "ipaNTDomainGUID" ATTR_FALLBACK_GROUP = "ipaNTFallbackPrimaryGroup" OBJC_USER = "ipaNTUserAttrs" OBJC_GROUP = "ipaNTGroupAttrs" OBJC_DOMAIN = "ipaNTDomainAttrs" FALLBACK_GROUP_NAME = u'Default SMB Group' def __init__(self, fstore=None): self.netbios_name = None self.reset_netbios_name = None self.add_sids = None self.smbd_user = None self.smb_dn_pwd = None self.trust_dn = None self.smb_dom_dn = None self.sub_dict = None self.rid_base = None self.secondary_rid_base = None self.fqdn = None self.host_netbios_name = None super(ADTRUSTInstance, self).__init__( "smb", service_desc="CIFS", fstore=fstore, service_prefix=u'cifs', keytab=paths.SAMBA_KEYTAB) self.__setup_default_attributes() def __setup_default_attributes(self): """ This method setups default attributes that are either constants, or based on api.env attributes, such as realm, hostname or domain name. """ # Constants self.smb_conf = paths.SMB_CONF self.cifs_hosts = [] # Values obtained from API.env self.fqdn = self.fqdn or api.env.host self.host_netbios_name = make_netbios_name(self.fqdn) self.realm = self.realm or api.env.realm self.suffix = ipautil.realm_to_suffix(self.realm) self.ldapi_socket = "%%2fvar%%2frun%%2fslapd-%s.socket" % \ installutils.realm_to_serverid(self.realm) # DN definitions self.trust_dn = DN(api.env.container_trusts, self.suffix) self.smb_dn = DN(('cn', 'adtrust agents'), ('cn', 'sysaccounts'), ('cn', 'etc'), self.suffix) self.smb_dom_dn = DN(('cn', api.env.domain), api.env.container_cifsdomains, self.suffix) self.cifs_agent = DN(('krbprincipalname', self.principal.lower()), api.env.container_service, self.suffix) self.host_princ = DN(('fqdn', self.fqdn), api.env.container_host, self.suffix) def __gen_sid_string(self): sub_ids = struct.unpack(" 1: # there are other CIFS services defined, we are not alone for entry in res: managedBy = entry.single_value.get('managedBy') if managedBy: fqdn = DN(managedBy)['fqdn'] if fqdn != unicode(self.fqdn): # this is CIFS service of a different host in our # REALM, we need to remember it to announce via # SRV records for _msdcs self.cifs_hosts.append(normalize_zone(fqdn)) except Exception as e: root_logger.critical("Checking replicas for cifs principals failed with error '%s'" % e) def __enable_compat_tree(self): try: compat_plugin_dn = DN("cn=Schema Compatibility,cn=plugins,cn=config") lookup_nsswitch_name = "schema-compat-lookup-nsswitch" for config in (("cn=users", "user"), ("cn=groups", "group")): entry_dn = DN(config[0], compat_plugin_dn) current = api.Backend.ldap2.get_entry(entry_dn) lookup_nsswitch = current.get(lookup_nsswitch_name, []) if not(config[1] in lookup_nsswitch): current[lookup_nsswitch_name] = [config[1]] api.Backend.ldap2.update_entry(current) except Exception as e: root_logger.critical("Enabling nsswitch support in slapi-nis failed with error '%s'" % e) def __validate_server_hostname(self): hostname = socket.gethostname() if hostname != self.fqdn: raise ValueError("Host reports different name than configured: " "'%s' versus '%s'. Samba requires to have " "the same hostname or Kerberos principal " "'cifs/%s' will not be found in Samba keytab." % (hostname, self.fqdn, self.fqdn)) def __start(self): try: self.start() services.service('winbind', api).start() except Exception: root_logger.critical("CIFS services failed to start") def __stop(self): self.backup_state("running", self.is_running()) try: services.service('winbind', api).stop() self.stop() except Exception: pass def __restart_dirsrv(self): try: installutils.restart_dirsrv() except Exception: pass def __restart_smb(self): try: services.knownservices.smb.restart() except Exception: pass def __enable(self): self.backup_state("enabled", self.is_enabled()) # We do not let the system start IPA components on its own, # Instead we reply on the IPA init script to start only enabled # components as found in our LDAP configuration tree # Note that self.dm_password is None for ADTrustInstance because # we ensure to be called as root and using ldapi to use autobind try: self.ldap_enable('ADTRUST', self.fqdn, None, self.suffix) except (ldap.ALREADY_EXISTS, errors.DuplicateEntry): root_logger.info("ADTRUST Service startup entry already exists.") try: self.ldap_enable('EXTID', self.fqdn, None, self.suffix) except (ldap.ALREADY_EXISTS, errors.DuplicateEntry): root_logger.info("EXTID Service startup entry already exists.") def __setup_sub_dict(self): self.sub_dict = dict(REALM = self.realm, SUFFIX = self.suffix, NETBIOS_NAME = self.netbios_name, HOST_NETBIOS_NAME = self.host_netbios_name, SMB_DN = self.smb_dn, LDAPI_SOCKET = self.ldapi_socket, FQDN = self.fqdn) def setup(self, fqdn, realm_name, netbios_name, reset_netbios_name, rid_base, secondary_rid_base, add_sids=False, smbd_user="samba", enable_compat=False): self.fqdn = fqdn self.realm = realm_name self.netbios_name = netbios_name self.reset_netbios_name = reset_netbios_name self.rid_base = rid_base self.secondary_rid_base = secondary_rid_base self.add_sids = add_sids self.enable_compat = enable_compat self.smbd_user = smbd_user # Setup constants and attributes derived from the values above self.__setup_default_attributes() self.__setup_sub_dict() def find_local_id_range(self): if api.Backend.ldap2.get_entries( DN(api.env.container_ranges, self.suffix), ldap.SCOPE_ONELEVEL, "(objectclass=ipaDomainIDRange)"): return try: entry = api.Backend.ldap2.get_entry( DN(('cn', 'admins'), api.env.container_group, self.suffix)) except errors.NotFound: raise ValueError("No local ID range and no admins group found.\n" \ "Add local ID range manually and try again!") base_id = int(entry.single_value['gidNumber']) id_range_size = 200000 id_filter = "(&" \ "(|(objectclass=posixAccount)" \ "(objectclass=posixGroup)" \ "(objectclass=ipaIDObject))" \ "(|(uidNumber<=%d)(uidNumber>=%d)" \ "(gidNumber<=%d)(gidNumner>=%d)))" % \ ((base_id - 1), (base_id + id_range_size), (base_id - 1), (base_id + id_range_size)) if api.Backend.ldap2.get_entries(DN(('cn', 'accounts'), self.suffix), ldap.SCOPE_SUBTREE, id_filter): raise ValueError("There are objects with IDs out of the expected" \ "range.\nAdd local ID range manually and try " \ "again!") entry = api.Backend.ldap2.make_entry( DN( ('cn', ('%s_id_range' % self.realm)), api.env.container_ranges, self.suffix), objectclass=['ipaDomainIDRange'], cn=['%s_id_range' % self.realm], ipaBaseID=[str(base_id)], ipaIDRangeSize=[str(id_range_size)], ) api.Backend.ldap2.add_entry(entry) def create_instance(self): self.step("validate server hostname", self.__validate_server_hostname) self.step("stopping smbd", self.__stop) self.step("creating samba domain object", \ self.__create_samba_domain_object) self.step("creating samba config registry", self.__write_smb_registry) self.step("writing samba config file", self.__write_smb_conf) self.step("adding cifs Kerberos principal", self.request_service_keytab) self.step("adding cifs and host Kerberos principals to the adtrust agents group", \ self.__setup_group_membership) self.step("check for cifs services defined on other replicas", self.__check_replica) self.step("adding cifs principal to S4U2Proxy targets", self.__add_s4u2proxy_target) self.step("adding admin(group) SIDs", self.__add_admin_sids) self.step("adding RID bases", self.__add_rid_bases) self.step("updating Kerberos config", self.__update_krb5_conf) self.step("activating CLDAP plugin", self.__add_cldap_module) self.step("activating sidgen task", self.__add_sidgen_task) self.step("configuring smbd to start on boot", self.__enable) self.step("adding special DNS service records", \ self.__add_dns_service_records) if self.enable_compat: self.step("enabling trusted domains support for older clients via Schema Compatibility plugin", self.__enable_compat_tree) self.step("restarting Directory Server to take MS PAC and LDAP plugins changes into account", \ self.__restart_dirsrv) self.step("adding fallback group", self.__add_fallback_group) self.step("adding Default Trust View", self.__add_default_trust_view) self.step("setting SELinux booleans", \ self.__configure_selinux_for_smbd) self.step("starting CIFS services", self.__start) if self.add_sids: self.step("adding SIDs to existing users and groups", self.__add_sids) self.step("restarting smbd", self.__restart_smb) self.start_creation(show_service_name=False) def uninstall(self): if self.is_configured(): self.print_msg("Unconfiguring %s" % self.service_name) # Call restore_state so that we do not leave mess in the statestore # Otherwise this does nothing self.restore_state("running") self.restore_state("enabled") winbind = services.service("winbind", api) # Always try to stop and disable smb service, since we do not leave # working configuration after uninstall try: self.stop() self.disable() winbind.stop() winbind.disable() except Exception: pass # Since we do not guarantee restoring back to working samba state, # we should not restore smb.conf # Restore the state of affected selinux booleans boolean_states = {name: self.restore_state(name) for name in constants.SELINUX_BOOLEAN_ADTRUST} try: tasks.set_selinux_booleans(boolean_states) except ipapython.errors.SetseboolError as e: self.print_msg('WARNING: ' + str(e)) # Remove samba's credentials cache installutils.remove_ccache(ccache_path=paths.KRB5CC_SAMBA) # Remove samba's configuration file installutils.remove_file(self.smb_conf) # Remove samba's persistent and temporary tdb files tdb_files = [tdb_file for tdb_file in os.listdir(paths.SAMBA_DIR) if tdb_file.endswith(".tdb")] for tdb_file in tdb_files: installutils.remove_file(tdb_file) # Remove our keys from samba's keytab self.clean_samba_keytab()