def save( self, request ): """Saves the UCS Active Directory Connector configuration options: LDAP_Host: hostname of the AD server LDAP_Base: LDAP base of the AD server LDAP_BindDN: LDAP DN to use for authentication KerberosDomain: kerberos domain PollSleep: time in seconds between polls RetryRejected: how many time to retry a synchronisation MappingSyncMode: synchronisation mode MappingGroupLanguage: language of the AD server return: { 'success' : (True|False), 'message' : <details> } """ self.required_options( request, *map( lambda x: x[ 0 ], Instance.OPTION_MAPPING ) ) self.guessed_baseDN = None try: fn = '%s/.htaccess' % DIR_WEB_AD fd = open( fn, 'w' ) fd.write( 'require user %s\n' % self._username ) fd.close() os.chmod( fn, 0644 ) os.chown( fn, 0, 0 ) except Exception, e: message = _( 'An error occured while saving .htaccess (filename=%(fn)s ; exception=%(exception)s)') % { 'fn': fn, 'exception': e.__class__.__name__ } MODULE.process( 'An error occured while saving .htaccess (filename=%(fn)s ; exception=%(exception)s)' % { 'fn': fn, 'exception': e.__class__.__name__ } ) self.finished( request.id, { 'success' : False, 'message' : message } ) return
def _thread( request ): result = [] for ldap_dn in request.options: if request.flavor == 'users/self': ldap_dn = self._user_dn module = get_module( request.flavor, ldap_dn ) if module is None: MODULE.process( 'A module for the LDAP DN %s could not be found' % ldap_dn ) continue obj = module.get( ldap_dn ) if obj: props = obj.info for passwd in module.password_properties: if passwd in props: del props[ passwd ] props[ '$dn$' ] = obj.dn props[ '$options$' ] = {} for opt in module.get_options( udm_object = obj ): props[ '$options$' ][ opt[ 'id' ] ] = opt[ 'value' ] props[ '$policies$' ] = {} for policy in obj.policies: pol_mod = get_module( None, policy ) if pol_mod and pol_mod.name: props[ '$policies$' ][ pol_mod.name ] = policy props[ '$labelObjectType$' ] = module.title; result.append( props ) else: MODULE.process( 'The LDAP object for the LDAP DN %s could not be found' % ldap_dn ) return result
def process_button(self): # if self._first_attempt_button: # self._first_attempt_button = False # raise UMC_Error(_('First try failed. Try again.')) MODULE.process('Generating new secret...') newkey_path = "/etc/openvpn/sitetosite.newkey" os.popen("openvpn --genkey --secret %s" % newkey_path) with open(newkey_path, 'r') as newkey_file: newkey=newkey_file.read().replace('\n', '<br />') return newkey
def _send_return( thread, result, request ): import traceback if not isinstance( result, BaseException ): MODULE.info( 'sending mail: completed successfully' ) self.finished( request.id, True ) else: msg = '%s\n%s: %s\n' % ( ''.join( traceback.format_tb( thread.exc_info[ 2 ] ) ), thread.exc_info[ 0 ].__name__, str( thread.exc_info[ 1 ] ) ) MODULE.process( 'sending mail:An internal error occurred: %s' % msg ) self.finished( request.id, False, msg, False )
def _check_thread_error( self, thread, result, request ): """Checks if the thread returned an exception. In that case in error response is send and the function returns True. Otherwise False is returned.""" if not isinstance( result, BaseException ): return False msg = '%s\n%s: %s\n' % ( ''.join( traceback.format_tb( thread.exc_info[ 2 ] ) ), thread.exc_info[ 0 ].__name__, str( thread.exc_info[ 1 ] ) ) MODULE.process( 'An internal error occurred: %s' % msg ) self.finished( request.id, None, msg, False ) return True
def _return( pid, status, bufstdout, bufstderr, request, fn ): success = True if status == 0: univention.config_registry.handler_set( [ u'connector/ad/ldap/certificate=%s' % fn ] ) message = _( 'Certificate has been uploaded successfully.' ) MODULE.info( 'Certificate has been uploaded successfully. status=%s\nSTDOUT:\n%s\n\nSTDERR:\n%s' % ( status, '\n'.join( bufstdout ), '\n'.join( bufstderr ) ) ) else: success = False message = _( 'Certificate upload or conversion failed.' ) MODULE.process( 'Certificate upload or conversion failed. status=%s\nSTDOUT:\n%s\n\nSTDERR:\n%s' % ( status, '\n'.join( bufstdout ), '\n'.join( bufstderr ) ) ) self.finished( request.id, [ { 'success' : success, 'message' : message } ] )
def _check_thread_error( self, thread, result, request ): """Checks if the thread returned an exception. In that case in error response is send and the function returns True. Otherwise False is returned.""" if not isinstance( result, BaseException ): return False if isinstance( result, (udm_errors.ldapSizelimitExceeded, udm_errors.ldapTimeout ) ): self.finished( request.id, None, result.args[0], success = False, status = MODULE_ERR_COMMAND_FAILED ) return True msg = '%s\n%s: %s\n' % ( ''.join( traceback.format_tb( thread.exc_info[ 2 ] ) ), thread.exc_info[ 0 ].__name__, str( thread.exc_info[ 1 ] ) ) MODULE.process( 'An internal error occurred: %s' % msg ) self.finished( request.id, None, msg, False ) return True
def _copy_certificate( self, request, error_if_missing = False ): ssldir = '/etc/univention/ssl/%s' % request.options.get( 'LDAP_Host' ) try: gid_wwwdata = grp.getgrnam('www-data')[2] except: gid_wwwdata = 0 if os.path.exists( ssldir ): for fn in ( 'private.key', 'cert.pem' ): dst = '%s/%s' % (DIR_WEB_AD, fn) try: shutil.copy2( '%s/%s' % ( ssldir, fn ), dst ) os.chmod( dst, 0440 ) os.chown( dst, 0, gid_wwwdata ) except Exception, e: MODULE.process( 'Copying of %s/%s to %s/%s failed (exception=%s)' % ( ssldir, fn, DIR_WEB_AD, fn, str( e.__class__ ) ) ) raise ConnectorError( _( 'Copying of %s/%s to %s/%s failed (exception=%s)') % ( ssldir, fn, DIR_WEB_AD, fn, e.__class__.__name__ ) )
def service( self, request ): MODULE.info( 'State: options=%s' % request.options ) self.required_options( request, 'action' ) self.__update_status() action = request.options[ 'action' ] MODULE.info( 'State: action=%s status_running=%s' % ( action, self.status_running ) ) success = True message = None if self.status_running and action == 'start': message = _( 'Active Directory Connector is already running. Nothing to do.' ) elif not self.status_running and action == 'stop': message =_( 'Active Directory Connector is already stopped. Nothing to do.' ) elif action not in ( 'start', 'stop' ): MODULE.process( 'State: unknown command: action=%s' % action ) message = _( 'Unknown command ("%s") Please report error to your local administrator' ) % action success = False if message is not None: self.finished( request.id, { 'success' : success, 'message' : message } ) return def _run_it( action ): return subprocess.call( ( 'invoke-rc.d', 'univention-ad-connector', action ) ) def _return( thread, result, request ): success = not result if result: message = _('Switching running state of Active Directory Connector failed.') MODULE.info( 'Switching running state of Active Directory Connector failed. exitcode=%s' % result ) else: if request.options.get( 'action' ) == 'start': message = _( 'UCS Active Directory Connector has been started.' ) else: message = _( 'UCS Active Directory Connector has been stopped.' ) self.finished( request.id, { 'success' : success, 'message' : message } ) cb = notifier.Callback( _return, request ) func = notifier.Callback( _run_it, action ) thread = notifier.threads.Simple( 'service', func, cb ) thread.run()
def _return( pid, status, buffer, request ): # dn: # namingContexts: DC=ad,DC=univention,DC=de # namingContexts: CN=Configuration,DC=ad,DC=univention,DC=de # namingContexts: CN=Schema,CN=Configuration,DC=ad,DC=univention,DC=de # namingContexts: DC=DomainDnsZones,DC=ad,DC=univention,DC=de # namingContexts: DC=ForestDnsZones,DC=ad,DC=univention,DC=de self.guessed_baseDN = None for line in buffer: if line.startswith( 'namingContexts: ' ): dn = line.split(': ',1)[1].strip() if self.guessed_baseDN is None or len( dn ) < len( self.guessed_baseDN ): self.guessed_baseDN = dn if self.guessed_baseDN is None: self.finished( request.id, { 'success' : False, 'message' : _('The LDAP base of the given Active Directory server could not be determined. Maybe the full-qualified hostname is wrong or unresolvable.' ) } ) MODULE.process( 'Could not determine baseDN of given ldap server. Maybe FQDN is wrong or unresolvable! FQDN=%s' % request.options[ 'baseDN' ] ) else: self.finished( request.id, { 'success' : True, 'LDAP_Base' : self.guessed_baseDN } ) MODULE.info( 'Guessed the LDAP base: %s' % self.guessed_baseDN )
def info_handler(self, info): MODULE.process(info) self.info = info
def handle(self, msg): # type: (Request) -> None """Handles incoming UMCP requests. This function is called only when it is a valid UMCP request. :param Request msg: the received UMCP request The following commands are handled directly and are not passed to the custom module code: * SET (acls|username|credentials) * EXIT """ from ..error import UMC_Error, NotAcceptable self.__time_remaining = self.__timeout PROTOCOL.info('Received UMCP %s REQUEST %s' % (msg.command, msg.id)) resp = Response(msg) resp.status = SUCCESS if msg.command == 'EXIT': shutdown_timeout = 100 MODULE.info("EXIT: module shutdown in %dms" % shutdown_timeout) # shutdown module after one second resp.message = 'module %s will shutdown in %dms' % ( msg.arguments[0], shutdown_timeout) self.response(resp) notifier.timer_add(shutdown_timeout, self._timed_out) return if self.__init_etype: notifier.timer_add(10000, self._timed_out) six.reraise(self.__init_etype, self.__init_exc, self.__init_etraceback) if msg.command == 'SET': for key, value in msg.options.items(): if key == 'acls': self.__acls = ACLs(acls=value) self.__handler.acls = self.__acls elif key == 'commands': self.__commands.fromJSON(value['commands']) elif key == 'username': self.__username = value self.__handler.username = self.__username elif key == 'password': self.__password = value self.__handler.password = self.__password elif key == 'auth_type': self.__auth_type = value self.__handler.auth_type = self.__auth_type elif key == 'credentials': self.__username = value['username'] self.__user_dn = value['user_dn'] self.__password = value['password'] self.__auth_type = value.get('auth_type') self.__handler.username = self.__username self.__handler.user_dn = self.__user_dn self.__handler.password = self.__password self.__handler.auth_type = self.__auth_type elif key == 'locale' and value is not None: try: self.__handler.update_language([value]) except NotAcceptable: pass # ignore if the locale doesn't exists, it continues with locale C else: raise UMC_Error(status=422) # if SET command contains 'acls', commands' and # 'credentials' it is the initialization of the module # process if 'acls' in msg.options and 'commands' in msg.options and 'credentials' in msg.options: MODULE.process('Initializing module.') try: self.__handler.init() except Exception: try: exc_info = sys.exc_info() self.__init_etype, self.__init_exc, self.__init_etraceback = exc_info # FIXME: do not keep a reference to traceback self.error_handling(msg, 'init', *exc_info) finally: exc_info = None return self.response(resp) return if msg.arguments: cmd = msg.arguments[0] cmd_obj = self.command_get(cmd) if cmd_obj and (not self.__check_acls or self.__acls.is_command_allowed( cmd, options=msg.options, flavor=msg.flavor)): self.__active_requests += 1 self.__handler.execute(cmd_obj.method, msg) return raise UMC_Error('Not initialized.', status=403)
def invoke(self, request): # ATTENTION!!!!!!! # this function has to stay compatible with the very first App Center installations (Dec 2012) # if you add new arguments that change the behaviour # you should add a new method (see invoke_dry_run) or add a function name (e.g. install-schema) # this is necessary because newer app center may talk remotely with older one # that does not understand new arguments and behaves the old way (in case of # dry_run: install application although they were asked to dry_run) host = request.options.get('host') function = request.options.get('function') send_as = function if function.startswith('install'): function = 'install' if function.startswith('update'): function = 'update' application_id = request.options.get('application') Application.all(only_local=True) # if not yet cached, cache. but use only local inis application = Application.find(application_id) if application is None: raise umcm.UMC_Error(_('Could not find an application for %s') % (application_id,)) force = request.options.get('force') only_dry_run = request.options.get('only_dry_run') dont_remote_install = request.options.get('dont_remote_install') only_master_packages = send_as.endswith('schema') MODULE.process('Try to %s (%s) %s on %s. Force? %r. Only master packages? %r. Prevent installation on other systems? %r. Only dry run? %r.' % (function, send_as, application_id, host, force, only_master_packages, dont_remote_install, only_dry_run)) # REMOTE invocation! if host and host != self.ucr.get('hostname'): try: client = Client(host, self.username, self.password) result = client.umc_command('appcenter/invoke', request.options).result except (ConnectionError, HTTPError) as exc: MODULE.error('Error during remote appcenter/invoke: %s' % (exc,)) result = { 'unreachable': [host], 'master_unreachable': True, 'serious_problems': True, 'software_changes_computed': True, # not really... } else: if result['can_continue']: def _thread_remote(_client, _package_manager): with _package_manager.locked(reset_status=True, set_finished=True): _package_manager.unlock() # not really locked locally, but busy, so "with locked()" is appropriate Application._query_remote_progress(_client, _package_manager) def _finished_remote(thread, result): if isinstance(result, BaseException): MODULE.warn('Exception during %s %s: %s' % (function, application_id, str(result))) thread = notifier.threads.Simple('invoke', notifier.Callback(_thread_remote, client, self.package_manager), _finished_remote) thread.run() self.finished(request.id, result) return # make sure that the application can be installed/updated can_continue = True delayed_can_continue = True serious_problems = False result = { 'install': [], 'remove': [], 'broken': [], 'unreachable': [], 'master_unreachable': False, 'serious_problems': False, 'hosts_info': {}, 'problems_with_hosts': False, 'serious_problems_with_hosts': False, 'invokation_forbidden_details': {}, 'invokation_warning_details': {}, 'software_changes_computed': False, } if not application: MODULE.process('Application not found: %s' % application_id) can_continue = False if can_continue and not only_master_packages: forbidden, warnings = application.check_invokation(function, self.package_manager) if forbidden: MODULE.process('Cannot %s %s: %r' % (function, application_id, forbidden)) result['invokation_forbidden_details'] = forbidden can_continue = False serious_problems = True if warnings: MODULE.process('Warning trying to %s %s: %r' % (function, application_id, forbidden)) result['invokation_warning_details'] = warnings if not force: # dont stop "immediately". # compute the package changes! delayed_can_continue = False result['serious_problems'] = serious_problems result['can_continue'] = can_continue try: if can_continue: if self._working(): # make it multi-tab safe (same session many buttons to be clicked) raise LockError() with self.package_manager.locked(reset_status=True): previously_registered_by_dry_run = False if can_continue and function in ('install', 'update'): remove_component = only_dry_run dry_run_result, previously_registered_by_dry_run = application.install_dry_run(self.package_manager, self.component_manager, remove_component=remove_component, username=self._username, password=self.password, only_master_packages=only_master_packages, dont_remote_install=dont_remote_install, function=function, force=force) result.update(dry_run_result) result['software_changes_computed'] = True serious_problems = bool(result['broken'] or result['master_unreachable'] or result['serious_problems_with_hosts']) if serious_problems or (not force and (result['unreachable'] or result['install'] or result['remove'] or result['problems_with_hosts'])): MODULE.process('Problems encountered or confirmation required. Removing component %s' % application.component_id) if not remove_component: # component was not removed automatically after dry_run if application.candidate: # operation on candidate failed. re-register original application application.register(self.component_manager, self.package_manager) else: # operation on self failed. unregister all application.unregister_all_and_register(None, self.component_manager, self.package_manager) can_continue = False elif can_continue and function in ('uninstall',) and not force: result['remove'] = application.uninstall_dry_run(self.package_manager) result['software_changes_computed'] = True can_continue = False can_continue = can_continue and delayed_can_continue and not only_dry_run result['serious_problems'] = serious_problems result['can_continue'] = can_continue if can_continue and not only_dry_run: def _thread(module, application, function): with module.package_manager.locked(set_finished=True): with module.package_manager.no_umc_restart(exclude_apache=True): if function in ('install', 'update'): # dont have to add component: already added during dry_run return application.install(module.package_manager, module.component_manager, add_component=only_master_packages, send_as=send_as, username=self._username, password=self.password, only_master_packages=only_master_packages, dont_remote_install=dont_remote_install, previously_registered_by_dry_run=previously_registered_by_dry_run) else: return application.uninstall(module.package_manager, module.component_manager, self._username, self.password) def _finished(thread, result): if isinstance(result, BaseException): MODULE.warn('Exception during %s %s: %s' % (function, application_id, str(result))) thread = notifier.threads.Simple('invoke', notifier.Callback(_thread, self, application, function), _finished) thread.run() else: self.package_manager.set_finished() # nothing to do, ready to take new commands self.finished(request.id, result) except LockError: # make it thread safe: another process started a package manager # this module instance already has a running package manager raise umcm.UMC_Error(_('Another package operation is in progress'))
def __error_handling(self, request, method, etype, exc, etraceback): """ Handle UMC exception. As requests are processes by python-notifier in a separate thread, any exception only contains the traceback relative to the notifier thread. To make them more usable we want to combine them with the calling part to get a complete stack trace. This is complicated by the fact that python-notifier no longer stores the original traceback, as this creates a memory leak. Instead only the rendered traceback is stored. :param request: The original UMC request. :param method: The failed UMC command. :param etype: The exception class. :param exc: The exception instance. :param etraceback: The exception traceback instance; may be None. """ message = '' result = None headers = None error = None trace = etraceback or [] if isinstance(etraceback, list): etraceback = None try: try: self.error_handling(etype, exc, etraceback) except: raise else: six.reraise(etype, exc, etraceback) except UMC_Error as exc: status = exc.status result = exc.result headers = exc.headers message = str(exc) if not exc.traceback and exc.include_traceback: exc.traceback = traceback.format_exc() if isinstance(exc.traceback, bytes): # Python 2 exc.traceback = exc.traceback.decode('utf-8', 'replace') error = { 'command': method, 'traceback': exc.traceback, } except: status = MODULE_ERR_COMMAND_FAILED if etraceback is None: # Bug #47114: thread.exc_info doesn't contain a traceback object anymore tb_str = ''.join(trace + traceback.format_exception_only( *sys.exc_info()[:2])) else: tb_str = traceback.format_exc() if isinstance(tb_str, bytes): # Python 2 tb_str = tb_str.decode('utf-8', 'replace') error = { 'command': ('%s %s' % (' '.join(request.arguments), '(%s)' % (request.flavor, ) if request.flavor else '')).strip(), 'traceback': tb_str, } if isinstance(error['command'], bytes): # Python 2 error['command'] = error['command'].decode('utf-8', 'replace') message = self._( 'Internal server error during "%(command)s".') % error MODULE.process(str(message)) self.finished(request.id, result, message, status=status, headers=headers, error=error)
def _step_handler(step): MODULE.process('Package manager progress: %.1f' % step) progress.current = (step / 100.0) * _nsteps + _step_offset
def admember_join(self, username, password, ad_server_address, progress): progress.title = _('Joining UCS into Active Directory domain') progress.total = 100.0 progress.warnings = [] overall_success = False MODULE.process(progress.title) def _progress(steps, msg): progress.current = float(steps) progress.message = msg MODULE.process(msg) time.sleep(0.2) def _err(exc=None, msg=None): exc_str = '' if exc is not None: exc_str = str(exc) or exc.__doc__ # if no message, take the doc string exc_class_name = exc.__class__.__name__ MODULE.error('Join process failed [%s]: %s' % (exc_class_name, exc_str)) if msg: MODULE.error(msg) else: msg = _('An unexpected error occurred: %s') % exc_str progress.finish_with_result({ 'success': False, 'error': msg, 'warnings': progress.warnings, }) ad_domain_info = {} try: admember.check_server_role() ad_domain_info = admember.lookup_adds_dc(ad_server_address) ad_server_ip = ad_domain_info['DC IP'] _progress(5, _('Configuring time synchronization...')) admember.time_sync(ad_server_ip) admember.set_timeserver(ad_server_ip) _progress(10, _('Configuring DNS server...')) admember.set_nameserver([ad_server_ip]) admember.prepare_ucr_settings() _progress(15, _('Configuring Kerberos settings...')) admember.disable_local_heimdal() admember.disable_local_samba4() _progress(20, _('Configuring reverse DNS settings...')) admember.prepare_dns_reverse_settings(ad_domain_info) _progress(25, _('Configuring software components...')) _step_offset = 30.0 _nsteps = 35.0 def _step_handler(step): MODULE.process('Package manager progress: %.1f' % step) progress.current = (step / 100.0) * _nsteps + _step_offset def _err_handler(err): MODULE.warn(err) progress.warnings.append(err) success = admember.remove_install_univention_samba(info_handler=MODULE.process, error_handler=_err_handler, step_handler=_step_handler) if not success: raise RuntimeError(_('An error occurred while installing necessary software components.')) _progress(65, _('Configuring synchronization from AD...')) admember.prepare_connector_settings(username, password, ad_domain_info) admember.disable_ssl() _progress(70, _('Renaming well known SID objects...')) admember.rename_well_known_sid_objects(username, password) _progress(75, _('Configuring Administrator account...')) admember.prepare_administrator(username, password) _progress(80, _('Running Samba join script...')) admember.run_samba_join_script(username, password) _progress(85, _('Configuring DNS entries...')) admember.add_domaincontroller_srv_record_in_ad(ad_server_ip, username, password) admember.add_host_record_in_ad(uid=username, bindpw=password, sso=True) admember.make_deleted_objects_readable_for_this_machine(username, password) admember.synchronize_account_position(ad_domain_info, username, password) _progress(90, _('Starting Active Directory connection service...')) admember.start_service('univention-ad-connector') _progress(95, _('Registering LDAP service entry...')) admember.add_admember_service_to_localhost() overall_success = True _progress(100, _('Join has been finished successfully.')) # error handling... except admember.invalidUCSServerRole as exc: _err(exc, _('The AD member mode can only be configured on a DC master server.')) except admember.failedADConnect as exc: _err(exc, _('Could not connect to AD Server %s. Please verify that the specified address is correct. (%s)') % (ad_domain_info.get('DC DNS Name'), "admember_join: %s" % (exc,))) except admember.domainnameMismatch as exc: _err(exc, _('The domain name of the AD Server (%(ad_domain)s) does not match the local UCS domain name (%(ucs_domain)s). For the AD member mode, it is necessary to setup a UCS system with the same domain name as the AD Server.') % {'ad_domain': ad_domain_info["Domain"], 'ucs_domain': ucr['domainname']}) except admember.connectionFailed as exc: _err(exc, _('Could not connect to AD Server %s. Please verify that username and password are correct.') % ad_domain_info.get('DC DNS Name')) except admember.failedToSetAdministratorPassword as exc: _err(exc, _('Failed to set the password of the UCS Administrator to the Active Directory Administrator password.')) except admember.timeSyncronizationFailed as exc: _err(exc, _('Could not synchronize the time between the UCS system and the Active Directory domain controller: %s') % exc) except RuntimeError as exc: _err(exc) except Exception as exc: # catch all other errors that are unlikely to occur _err(exc) MODULE.error('Traceback:\n%s' % traceback.format_exc()) if not overall_success: _progress(100, _('Join has been finished with errors.')) admember.revert_ucr_settings() admember.revert_connector_settings() if hasattr(progress, 'result'): # some error probably occurred -> return the result in the progress return progress.result return {'success': success}
def _install_dry_run_remote(self, app, function, dont_remote_install, force): MODULE.process('Invoke install_dry_run_remote') self.ucr.load() if function.startswith('upgrade'): remote_function = 'update-schema' else: remote_function = 'install-schema' master_packages = app.default_packages_master # connect to Primary/Backup Nodes unreachable = [] hosts_info = {} remote_info = { 'master_unreachable': False, 'problems_with_hosts': False, 'serious_problems_with_hosts': False, } dry_run_threads = [] info = get_action('info') if master_packages and not dont_remote_install: hosts = find_hosts_for_master_packages() # checking remote host is I/O heavy, so use threads # "global" variables: unreachable, hosts_info, remote_info def _check_remote_host(app_id, host, host_is_master, username, password, force, remote_function): MODULE.process('Starting dry_run for %s on %s' % (app_id, host)) MODULE.process('%s: Connecting...' % host) try: client = Client(host, username, password) except (ConnectionError, HTTPError) as exc: MODULE.warn('_check_remote_host: %s: %s' % (host, exc)) unreachable.append(host) if host_is_master: remote_info['master_unreachable'] = True else: MODULE.process('%s: ... done' % host) host_info = {} MODULE.process('%s: Getting version...' % host) try: host_version = client.umc_command( 'appcenter/version', { 'version': info.get_compatibility() }).result except Forbidden: # command is not yet known (older app center) MODULE.process('%s: ... forbidden!' % host) host_version = None except (ConnectionError, HTTPError) as exc: MODULE.warn('%s: Could not get appcenter/version: %s' % (host, exc)) raise except Exception as exc: MODULE.error('%s: Exception: %s' % (host, exc)) raise MODULE.process('%s: ... done' % host) host_info['compatible_version'] = info.is_compatible( host_version) MODULE.process('%s: Invoking %s ...' % (host, remote_function)) try: host_info['result'] = client.umc_command( 'appcenter/invoke_dry_run', { 'function': remote_function, 'application': app_id, 'force': force, 'dont_remote_install': True, }).result except Forbidden: # command is not yet known (older app center) MODULE.process('%s: ... forbidden!' % host) host_info['result'] = { 'can_continue': False, 'serious_problems': False } except (ConnectionError, HTTPError) as exc: MODULE.warn('Could not get appcenter/version: %s' % (exc, )) raise MODULE.process('%s: ... done' % host) if not host_info['compatible_version'] or not host_info[ 'result']['can_continue']: remote_info['problems_with_hosts'] = True if host_info['result'][ 'serious_problems'] or not host_info[ 'compatible_version']: remote_info['serious_problems_with_hosts'] = True hosts_info[host] = host_info MODULE.process('Finished dry_run for %s on %s' % (app_id, host)) for host, host_is_master in hosts: thread = Thread(target=_check_remote_host, args=(app.id, host, host_is_master, self.username, self.password, force, remote_function)) thread.start() dry_run_threads.append(thread) result = {} for thread in dry_run_threads: thread.join() MODULE.process('All %d threads finished' % (len(dry_run_threads))) result['unreachable'] = unreachable result['hosts_info'] = hosts_info result.update(remote_info) return result
def info_handler(self, info: str) -> None: MODULE.process(info) self.info = info
def _thread(): # perform all actions inside a thread... # collect files progress.component(_('Collecting exam files...')) if project: project.collect() progress.add_steps(10) # open a new connection to the master UMC master = ucr['ldap/master'] try: client = Client(master) client.authenticate_with_machine_account() except (ConnectionError, HTTPError) as exc: MODULE.error('Could not connect to UMC on %s: %s' % (master, exc)) raise UMC_Error( _('Could not connect to master server %s.') % (master, )) school = SchoolSearchBase.getOU(request.options['room']) # unset exam mode for the given computer room progress.component(_('Configuring the computer room...')) client.umc_command( 'schoolexam-master/unset-computerroom-exammode', dict( roomdn=request.options['room'], school=school, )).result progress.add_steps(5) # delete exam users accounts if project: # get a list of user accounts in parallel exams parallelUsers = dict([ (iuser.username, iproject.description) for iproject in util.distribution.Project.list() if iproject.name != project.name for iuser in iproject.recipients ]) progress.component(_('Removing exam accounts')) percentPerUser = 25.0 / (1 + len(project.recipients)) for iuser in project.recipients: progress.info( '%s, %s (%s)' % (iuser.lastname, iuser.firstname, iuser.username)) try: if iuser.username not in parallelUsers: # remove first the home directory shutil.rmtree(iuser.unixhome, ignore_errors=True) # remove LDAP user entry client.umc_command( 'schoolexam-master/remove-exam-user', dict( userdn=iuser.dn, school=school, )).result MODULE.info('Exam user has been removed: %s' % iuser.dn) else: MODULE.process( 'Cannot remove the user account %s as it is registered for the running exam "%s", as well' % (iuser.dn, parallelUsers[iuser.username])) except (ConnectionError, HTTPError) as e: MODULE.warn( 'Could not remove exam user account %s: %s' % (iuser.dn, e)) # indicate the user has been processed progress.add_steps(percentPerUser) progress.add_steps(percentPerUser)
def read_syntax_choices(syn, options={}, module_search_options={}, ldap_connection=None, ldap_position=None): syntax_name = syn.name choices = getattr(syn, 'choices', []) if issubclass(syn.__class__, udm_syntax.UDM_Objects): choices = [] # try to avoid using the slow udm interface simple = False attr = set() if not syn.use_objects: attr.update(re.findall(r'%\(([^)]+)\)', syn.key)) if syn.label: attr.update(re.findall(r'%\(([^)]+)\)', syn.label)) for udm_module in syn.udm_modules: module = UDM_Module(udm_module) if not module.allows_simple_lookup(): break if module is not None: mapping = module.module.mapping if not all([mapping.mapName(att) for att in attr]): break else: simple = True if not simple: MODULE.warn('Syntax %s wants to get optimizations but may not. This is a Bug! We provide a fallback but the syntax will respond much slower than it could!' % syntax_name) def extract_key_label(syn, dn, info): key = label = None if syn.key == 'dn': key = dn else: try: key = syn.key % info except KeyError: pass if syn.label == 'dn': label = dn elif syn.label is None: pass else: try: label = syn.label % info except KeyError: pass return key, label if not simple: def map_choices(obj_list): result = [] for obj in obj_list: # first try it without obj.open() (expensive) key, label = extract_key_label(syn, obj.dn, obj.info) if key is None or label is None: obj.open() key, label = extract_key_label(syn, obj.dn, obj.info) if key is None: # ignore the entry as the key is important for a selection, there # is no sensible fallback for the key (Bug #26994) continue if label is None: # fallback to the default description as this is just what displayed # to the user (Bug #26994) label = udm_objects.description(obj) result.append((key, label)) return result for udm_module in syn.udm_modules: module = UDM_Module(udm_module) if module.module is None: continue filter_s = _create_ldap_filter(syn, options, module) if filter_s is not None: search_options = {'filter': filter_s} search_options.update(module_search_options) choices.extend(map_choices(module.search(**search_options))) else: for udm_module in syn.udm_modules: module = UDM_Module(udm_module) if module.module is None: continue filter_s = _create_ldap_filter(syn, options, module) if filter_s is not None: if filter_s and not filter_s.startswith('('): filter_s = '(%s)' % filter_s mapping = module.module.mapping ldap_attr = [mapping.mapName(att) for att in attr] search_options = {'filter': filter_s, 'simple': True} search_options.update(module_search_options) if ldap_attr: search_options['simple_attrs'] = ldap_attr result = module.search(**search_options) for dn, ldap_map in result: info = udm_mapping.mapDict(mapping, ldap_map) key, label = extract_key_label(syn, dn, info) if key is None: continue if label is None: label = ldap_connection.explodeDn(dn, 1)[0] choices.append((key, label)) else: keys = module.search(**search_options) if syn.label == 'dn': labels = keys else: labels = [ldap_connection.explodeDn(dn, 1)[0] for dn in keys] choices.extend(zip(keys, labels)) elif issubclass(syn.__class__, udm_syntax.UDM_Attribute): choices = [] def filter_choice(obj): # if attributes does not exist or is empty return syn.attribute in obj.info and obj.info[syn.attribute] def map_choice(obj): obj.open() MODULE.info('Loading choices from %s: %s' % (obj.dn, obj.info)) try: values = obj.info[syn.attribute] except KeyError: MODULE.warn('Object has no attribute %r' % (syn.attribute,)) # this happens for example in PrinterDriverList # if the ldap schema is not installed # and thus no 'printmodel' attribute is known. return [] if not isinstance(values, (list, tuple)): # single value values = [values] if syn.is_complex: return [(x[syn.key_index], x[syn.label_index]) for x in values] if syn.label_format is not None: _choices = [] for value in values: obj.info['$attribute$'] = value _choices.append((value, syn.label_format % obj.info)) return _choices return [(x, x) for x in values] module = UDM_Module(syn.udm_module) if module.module is None: return [] MODULE.info('Found syntax %s with udm_module property' % syntax_name) if syn.udm_filter == 'dn': choices = map_choice(module.get(options[syn.depends])) else: filter_s = _create_ldap_filter(syn, options, module) if filter_s is not None: for element in map(map_choice, filter(filter_choice, module.search(filter=filter_s))): for item in element: choices.append(item) elif issubclass(syn.__class__, udm_syntax.ldapDn) and hasattr(syn, 'searchFilter'): try: result = ldap_connection.searchDn(filter=syn.searchFilter) except udm_errors.base: MODULE.process('Failed to initialize syntax class %s' % syntax_name) return [] choices = [] for dn in result: dn_list = ldap_connection.explodeDn(dn) choices.append((dn, dn_list[0].split('=', 1)[1])) choices = [{'id': x[0], 'label': x[1]} for x in choices] if issubclass(syn.__class__, udm_syntax.LDAP_Search): options = options.get('options', {}) try: syntax = udm_syntax.LDAP_Search(options['syntax'], options['filter'], options['attributes'], options['base'], options['value'], options['viewonly'], options['empty'], options['empty_end']) except KeyError: syntax = syn if '$dn$' in options: filter_mod = get_module(None, options['$dn$']) if filter_mod: obj = filter_mod.get(options['$dn$']) syntax.filter = udm.pattern_replace(syntax.filter, obj) syntax._prepare(ldap_connection, syntax.filter) choices = [] for item in syntax.values: if syntax.viewonly: dn, display_attr = item else: dn, store_pattern, display_attr = item if display_attr: # currently we just support one display attribute mod_display, display = split_module_attr(display_attr[0]) module = get_module(mod_display, dn) # mod_display might be None else: module = get_module(None, dn) display = None if not module: continue obj = module.get(dn) if not obj: continue # find the value to store if not syntax.viewonly: mod_store, store = split_module_attr(store_pattern) if store == 'dn': id = dn elif store in obj: id = obj[store] elif store in obj.oldattr and obj.oldattr[store]: id = obj.oldattr[store][0] else: # no valid store object, ignore MODULE.warn('LDAP_Search syntax %r: %r is no valid property for object %r - ignoring entry.' % (syntax.name, store, dn)) continue # find the value to display if display == 'dn': label = dn elif display is None: # if view-only and in case of error label = '%s: %s' % (module.title, obj[module.identifies]) else: if display in obj: label = obj[display] elif display in obj.oldattr and obj.oldattr[display]: label = obj.oldattr[display][0] else: label = 'Unknown attribute %s' % display # create list entry if syntax.viewonly: choices.append({'module': 'udm', 'flavor': module.flavor or 'navigation', 'objectType': module.name, 'id': dn, 'label': label, 'icon': 'udm-%s' % module.name.replace('/', '-')}) else: choices.append({'module': 'udm', 'flavor': module.flavor or 'navigation', 'objectType': module.name, 'id': id, 'label': label, 'icon': 'udm-%s' % module.name.replace('/', '-')}) # sort choices before inserting / appending some special items choices = sorted(choices, key=lambda choice: choice['label']) if issubclass(syn.__class__, (udm_syntax.UDM_Objects, udm_syntax.UDM_Attribute)): if isinstance(syn.static_values, (tuple, list)): for value in syn.static_values: choices.insert(0, {'id': value[0], 'label': value[1]}) if syn.empty_value: choices.insert(0, {'id': '', 'label': ''}) elif issubclass(syn.__class__, udm_syntax.LDAP_Search): # then append empty value if syntax.addEmptyValue: choices.insert(0, {'id': '', 'label': ''}) elif syntax.appendEmptyValue: choices.append({'id': '', 'label': ''}) return choices
def init(self): self.azure_response = None self.adconnection_alias = ucr.get(adconnection_wizard_ucrv) or None MODULE.process('adconnection_alias={!r}'.format(self.adconnection_alias))
def run(cmd, stepsPerScript, info_handler=_dummyFunc, error_handler=_dummyFunc, critical_handler=_dummyFunc, step_handler=_dummyFunc, component_handler=_dummyFunc): # disable UMC/apache restart MODULE.info('disabling UMC and apache server restart') subprocess.call(CMD_DISABLE_EXEC) try: # regular expressions for output parsing regError = re.compile('^\* Message:\s*(?P<message>.*)\s*$') regJoinScript = re.compile( '(Configure|Running)\s+(?P<script>.*)\.inst.*$') regInfo = re.compile('^(?P<message>.*?)\s*:?\s*\x1b.*$') # call to univention-join MODULE.info('calling "%s"' % ' '.join(cmd)) process = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) failedJoinScripts = [] while True: # get the next line line = process.stdout.readline() if not line: # no more text from stdout break MODULE.process(line.strip()) # parse output... first check for errors m = regError.match(line) if m: message = m.groupdict().get('message') error_handler( _("The system join process could not be completed:<br/><br/><i>%s</i><br/><br/> More details can be found in the log file <i>/var/log/univention/join.log</i>.<br/>Please retry after resolving any conflicting issues." ) % message) if message.startswith('ssh-login for') or message.startswith( 'binddn for'): # invalid credentials or non existent user # do a critical error, the script will stop here critical_handler(True) continue # check for currently called join script m = regJoinScript.match(line) if m: component_handler(_('Executing join scripts')) info_handler( _('Executing join script %s') % m.groupdict().get('script')) step_handler(stepsPerScript) if 'failed' in line: failedJoinScripts.append(m.groupdict().get('script')) continue # check for other information m = regInfo.match(line) if m: info_handler(m.groupdict().get('message')) step_handler(stepsPerScript / 10) continue # get all remaining output stdout, stderr = process.communicate() if stderr: # write stderr into the log file MODULE.warn('stderr: %s' % stderr) success = True # check for errors if process.returncode != 0: # error case MODULE.warn('Could not perform system join: %s%s' % (stdout, stderr)) success = False elif failedJoinScripts: MODULE.warn( 'The following join scripts could not be executed: %s' % failedJoinScripts) error_handler( _('Some join scripts could not be executed. More details can be found in the log file <i>/var/log/univention/join.log</i>.<br/>Please retry to execute the join scripts after resolving any conflicting issues.' )) success = False return success finally: # make sure that UMC servers and apache can be restarted again MODULE.info('enabling UMC and apache server restart') subprocess.call(CMD_ENABLE_EXEC)
class Instance(Base, ProgressMixin): OPTION_MAPPING = ( ('LDAP_Host', 'connector/ad/ldap/host', ''), ('LDAP_Base', 'connector/ad/ldap/base', ''), ('LDAP_BindDN', 'connector/ad/ldap/binddn', ''), ('KerberosDomain', 'connector/ad/mapping/kerberosdomain', ''), ('PollSleep', 'connector/ad/poll/sleep', 5), ('RetryRejected', 'connector/ad/retryrejected', 10), ('DebugLevel', 'connector/debug/level', 2), ('DebugFunction', 'connector/debug/function', False), ('MappingSyncMode', 'connector/ad/mapping/syncmode', 'sync'), ('MappingGroupLanguage', 'connector/ad/mapping/group/language', 'de') ) def init(self): self.__update_status() def state(self, request): """Retrieve current status of the Active Directory connection configuration and the service options: {} return: { 'configured' : (True|False), 'certificate' : (True|False), 'running' : (True|False) } """ self.__update_status() self.finished(request.id, { 'ssl_enabled': self.status_ssl, 'password_sync_enabled': self.status_password_sync, 'running': self.status_running, 'certificate': self.status_certificate, 'mode_admember': self.status_mode_admember, 'mode_adconnector': self.status_mode_adconnector, 'configured': self.status_mode_adconnector or self.status_mode_admember, 'server_role': ucr.get('server/role'), }) def load(self, request): """Retrieve current status of the Active Directory connection configuration and the service options: {} return: { <all AD connector UCR variables> } """ result = {} for option, var, default in Instance.OPTION_MAPPING: result[option] = ucr.get(var, default) pwd_file = ucr.get('connector/ad/ldap/bindpw') result['passwordExists'] = bool(pwd_file and os.path.exists(pwd_file)) self.finished(request.id, result) @sanitize(LDAP_Host=StringSanitizer(required=True)) def adconnector_save(self, request): """Saves the Active Directory connection configuration options: Host_IP: IP address of the AD server LDAP_Host: hostname of the AD server LDAP_Base: LDAP base of the AD server LDAP_BindDN: LDAP DN to use for authentication KerberosDomain: kerberos domain PollSleep: time in seconds between polls RetryRejected: how many time to retry a synchronisation MappingSyncMode: synchronisation mode MappingGroupLanguage: language of the AD server return: { 'success' : (True|False), 'message' : <details> } """ self.required_options(request, 'Host_IP') self.required_options(request, *[x[0] for x in Instance.OPTION_MAPPING if x[2] == '']) for umckey, ucrkey, default in Instance.OPTION_MAPPING: val = request.options.get(umckey, default) if val: if isinstance(val, bool): val = val and 'yes' or 'no' MODULE.info('Setting %s=%s' % (ucrkey, val)) univention.config_registry.handler_set([u'%s=%s' % (ucrkey, val)]) ucr.load() if ucr.get('connector/ad/ldap/ldaps'): MODULE.info('Unsetting connector/ad/ldap/ldaps') univention.config_registry.handler_unset([u'connector/ad/ldap/ldaps']) if ucr.get('connector/ad/ldap/port') == '636': MODULE.info('Setting ldap port to 389') univention.config_registry.handler_set([u'connector/ad/ldap/port=389']) if not request.options.get('LDAP_Password') in (None, '', DO_NOT_CHANGE_PWD): fn = ucr.get('connector/ad/ldap/bindpw', FN_BINDPW) try: fd = open(fn, 'w') fd.write(request.options.get('LDAP_Password')) fd.close() os.chmod(fn, 0600) os.chown(fn, 0, 0) univention.config_registry.handler_set([u'connector/ad/ldap/bindpw=%s' % fn]) except Exception, e: MODULE.info('Saving bind password failed (filename=%(fn)s ; exception=%(exception)s)' % {'fn': fn, 'exception': str(e.__class__)}) self.finished(request.id, {'success': False, 'message': _('Saving bind password failed (filename=%(fn)s ; exception=%(exception)s)') % {'fn': fn, 'exception': str(e.__class__)}}) return ssldir = '/etc/univention/ssl/%s' % request.options.get('LDAP_Host') if not os.path.exists(ssldir): self._create_certificate(request) return # enter a static host entry such that the AD server's FQDN can be resolved univention.config_registry.handler_set([u'hosts/static/%(Host_IP)s=%(LDAP_Host)s' % request.options]) # check for SSL support on AD side if admember.server_supports_ssl(server=request.options.get('LDAP_Host')): MODULE.process('Enabling SSL...') admember.enable_ssl() else: MODULE.warn('SSL is not supported') admember.disable_ssl() # UCR variables are set, and now we can try to guess the language of # the AD domain ad_lang = guess_ad_domain_language() univention.config_registry.handler_set([u'connector/ad/mapping/group/language=%s' % ad_lang]) self.finished(request.id, {'success': True, 'message': _('Active Directory connection settings have been saved.')})
def save_csv(self, request): result = {} file_id = generate_random() school = request.body['school'] user_type = request.body['type'] delete_not_mentioned = bool(request.body.get('delete_not_mentioned')) user_klass = None if user_type == 'student': user_klass = CSVStudent elif user_type == 'teacher': user_klass = CSVTeacher elif user_type == 'staff': user_klass = CSVStaff elif user_type == 'teachersAndStaff': user_klass = CSVTeachersAndStaff filename = request.options[0]['tmpfile'] MODULE.process('Processing %s' % filename) sniffer = csv.Sniffer() with open(filename, 'rb') as f: content = f.read() # try utf-8 and latin-1 # if this still fails -> traceback... try: content = content.decode('utf-8') except UnicodeDecodeError: content = content.decode('latin-1') lines = content.splitlines() try: first_line = '' if lines: first_line = lines[0].strip() try: MODULE.process('First line is:\n%s' % first_line) except TypeError: MODULE.error( 'First line is not printable! Wrong CSV format!') try: # be strict regarding delimiters. I have seen the # sniffer returning 'b' as the delimiter in some cases dialect = sniffer.sniff(str(content), delimiters=' ,;\t') dialect.strict = True except csv.Error: # Something went wrong. But the sniffer is not exact # try it again the hard way: delimiter=, quotechar=" MODULE.warn( 'Error while sniffing csv dialect... fallback to excel') dialect = csv.get_dialect('excel') # Is the first line the header or already a user? # sniffer.has_header(content) # is not very acurate. We know how a header looks like... has_header = user_klass.is_header(first_line, dialect) if has_header: MODULE.process('Seems to be a header...') num_lines = 0 with open(filename, 'wb') as f: reader = csv.reader(lines, dialect) writer = csv.writer(f, dialect) csv_columns = [] # Benutzername,Vorname,Irgendwas columns = [] # name,firstname,unused2 first_lines = [] for line in reader: line = [cell.strip() for cell in line] if not any(line): # empty line continue writer.writerow(line) if not csv_columns: if has_header: csv_columns = line continue else: csv_columns = [ 'unused%d' % x for x in range(len(line)) ] if len(first_lines) < 10: first_lines.append(line) num_lines += 1 if num_lines == 0: raise csv.Error('Empty!') for i, column in enumerate(csv_columns): column_name = user_klass.find_field_name_from_label( column, i) if column_name in columns: column_name = 'unused%d' % i columns.append(column_name) MODULE.process('First line translates to columns: %r' % columns) except csv.Error as exc: MODULE.warn('Malformatted CSV file? %s' % exc) result['success'] = False # result['message'] = '' self.finished(request.id, [result]) else: self.file_map[file_id] = FileInfo(filename, school, user_klass, dialect, has_header, delete_not_mentioned) result['success'] = True result['filename'] = filename result['file_id'] = file_id result['available_columns'] = user_klass.get_columns_for_assign() result['given_columns'] = columns result['first_lines'] = first_lines result['num_more_lines'] = num_lines - len(first_lines) self.finished(request.id, [result])
def _progress(steps, msg): progress.current = float(steps) progress.message = msg MODULE.process(msg) time.sleep(0.2)
def get_master(lo): MODULE.process('Searching DC Master') return get_hosts(domaincontroller_master, lo)[0].info['fqdn']
def put(self, request, ldap_machine_write=None, ldap_user_read=None, ldap_position=None): """Returns the objects for the given IDs requests.options = [ { object : ..., options : ... }, ... ] return: True|<error message> """ if request.flavor == 'teacher': request.options = request.options[0]['object'] return self.add_teacher_to_classes(request) klass = get_group_class(request) for group_from_umc in request.options: group_from_umc = group_from_umc['object'] group_from_umc_dn = group_from_umc['$dn$'] break try: group_from_ldap = klass.from_dn(group_from_umc_dn, None, ldap_machine_write) except udm_exceptions.noObject: raise UMC_Error('unknown group object') old_members = self._filter_members(request, group_from_ldap, group_from_ldap.users, ldap_user_read) removed_members = set(o['id'] for o in old_members) - set( group_from_umc['members']) MODULE.info('Modifying group "%s" with members: %s' % (group_from_ldap.dn, group_from_ldap.users)) MODULE.info('New members: %s' % group_from_umc['members']) MODULE.info('Removed members: %s' % (removed_members, )) if request.flavor == 'workgroup-admin': # do not allow groups to be renamed in order to avoid conflicts with shares # grp.name = '%(school)s-%(name)s' % group group_from_ldap.description = group_from_umc['description'] # Workgroup admin view → update teachers, admins, students, (staff) # Class view → update only the group's teachers (keep all non teachers) # Workgroup teacher view → update only the group's students users = [] # keep specific users from the group for userdn in group_from_ldap.users: try: user = User.from_dn(userdn, None, ldap_machine_write) except udm_exceptions.noObject: # no permissions/is not a user/does not exists → keep the old value users.append(userdn) continue if not user.schools or not set(user.schools) & set( [group_from_ldap.school]): users.append(userdn) continue if (request.flavor == 'class' and not user.is_teacher(ldap_machine_write)) or ( request.flavor == 'workgroup' and not user.is_student(ldap_machine_write) ) or request.flavor == 'workgroup-admin': users.append(userdn) # add only certain users to the group for userdn in group_from_umc['members']: try: user = User.from_dn(userdn, None, ldap_machine_write) except udm_exceptions.noObject as exc: MODULE.error('Not adding not existing user %r to group: %r.' % (userdn, exc)) continue if not user.schools or not set(user.schools) & set( [group_from_ldap.school]): raise UMC_Error( _('User %s does not belong to school %r.') % (Display.user(user.get_udm_object(ldap_machine_write)), group_from_ldap.school)) if request.flavor == 'workgroup-admin' and not user.is_student( ldap_machine_write) and not user.is_administrator( ldap_machine_write) and not user.is_staff( ldap_machine_write) and not user.is_teacher( ldap_machine_write): raise UMC_Error( _('User %s does not belong to school %r.') % (Display.user(user.get_udm_object(ldap_machine_write)), group_from_ldap.school)) if request.flavor == 'class' and not user.is_teacher( ldap_machine_write): raise UMC_Error( _('User %s is not a teacher.') % (Display.user(user.get_udm_object(ldap_machine_write)), )) if request.flavor == 'workgroup' and not user.is_student( ldap_machine_write): raise UMC_Error( _('User %s is not a student.') % (Display.user(user.get_udm_object(ldap_machine_write)), )) users.append(user.dn) group_from_ldap.users = list(set(users) - removed_members) try: success = group_from_ldap.modify(ldap_machine_write) MODULE.info('Modified, group has now members: %s' % (group_from_ldap.users, )) except udm_exceptions.base as exc: MODULE.process('An error occurred while modifying "%s": %s' % (group_from_umc['$dn$'], exc.message)) raise UMC_Error(_('Failed to modify group (%s).') % exc.message) self.finished(request.id, success)
def user_dn(self, user_dn): self._user_dn = user_dn MODULE.process('Setting user LDAP DN %r' % (self._user_dn, ))
def _check_remote_host(app_id, host, host_is_master, username, password, force, remote_function): MODULE.process('Starting dry_run for %s on %s' % (app_id, host)) MODULE.process('%s: Connecting...' % host) try: client = Client(host, username, password) except (ConnectionError, HTTPError) as exc: MODULE.warn('_check_remote_host: %s: %s' % (host, exc)) unreachable.append(host) if host_is_master: remote_info['master_unreachable'] = True else: MODULE.process('%s: ... done' % host) host_info = {} MODULE.process('%s: Getting version...' % host) try: host_version = client.umc_command( 'appcenter/version', { 'version': info.get_compatibility() }).result except Forbidden: # command is not yet known (older app center) MODULE.process('%s: ... forbidden!' % host) host_version = None except (ConnectionError, HTTPError) as exc: MODULE.warn('%s: Could not get appcenter/version: %s' % (host, exc)) raise except Exception as exc: MODULE.error('%s: Exception: %s' % (host, exc)) raise MODULE.process('%s: ... done' % host) host_info['compatible_version'] = info.is_compatible( host_version) MODULE.process('%s: Invoking %s ...' % (host, remote_function)) try: host_info['result'] = client.umc_command( 'appcenter/invoke_dry_run', { 'function': remote_function, 'application': app_id, 'force': force, 'dont_remote_install': True, }).result except Forbidden: # command is not yet known (older app center) MODULE.process('%s: ... forbidden!' % host) host_info['result'] = { 'can_continue': False, 'serious_problems': False } except (ConnectionError, HTTPError) as exc: MODULE.warn('Could not get appcenter/version: %s' % (exc, )) raise MODULE.process('%s: ... done' % host) if not host_info['compatible_version'] or not host_info[ 'result']['can_continue']: remote_info['problems_with_hosts'] = True if host_info['result'][ 'serious_problems'] or not host_info[ 'compatible_version']: remote_info['serious_problems_with_hosts'] = True hosts_info[host] = host_info MODULE.process('Finished dry_run for %s on %s' % (app_id, host))
def system_join(username, password, info_handler, error_handler, step_handler): # make sure we got the correct server role server_role = ucr.get('server/role') assert server_role in ('domaincontroller_slave', 'domaincontroller_backup', 'domaincontroller_master') # get the number of join scripts n_joinscripts = len(glob.glob('/usr/lib/univention-install/*.inst')) steps_per_script = 100.0 / n_joinscripts # disable UMC/apache restart MODULE.info('disabling UMC and apache server restart') subprocess.call(CMD_DISABLE_EXEC) try: with tempfile.NamedTemporaryFile() as password_file: password_file.write('%s' % password) password_file.flush() # regular expressions for output parsing error_pattern = re.compile('^\* Message:\s*(?P<message>.*)\s*$') joinscript_pattern = re.compile('(Configure|Running)\s+(?P<script>.*)\.inst.*$') info_pattern = re.compile('^(?P<message>.*?)\s*:?\s*\x1b.*$') # call to univention-join process = None if server_role == 'domaincontroller_slave': # DC slave -> complete re-join MODULE.process('Performing system join...') process = subprocess.Popen(['/usr/sbin/univention-join', '-dcaccount', username, '-dcpwd', password_file.name], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) else: # DC backup/master -> only run join scripts MODULE.process('Executing join scripts ...') process = subprocess.Popen(['/usr/sbin/univention-run-join-scripts', '-dcaccount', username, '-dcpwd', password_file.name], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) failed_join_scripts = [] executed_join_scripts = set() def parse(line): if not line.strip(): return MODULE.process(repr(line.strip()).strip('"\'')) # parse output... first check for errors m = error_pattern.match(line) if m: error_handler(_('Software packages have been installed, however, the system join could not be completed: %s. More details can be found in the log file /var/log/univention/join.log. Please retry the join process via the UMC module "Domain join" after resolving any conflicting issues.') % m.groupdict().get('message')) return # check for currently called join script m = joinscript_pattern.match(line) if m: current_script = m.groupdict().get('script') info_handler(_('Executing join script %s') % (current_script,)) if current_script not in executed_join_scripts: executed_join_scripts.add(current_script) step_handler(steps_per_script) if 'failed' in line: failed_join_scripts.append(current_script) return # check for other information m = info_pattern.match(line) if m: info_handler(m.groupdict().get('message')) return # make stdout file descriptor of the process non-blocking fd = process.stdout.fileno() fl = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) unfinished_line = '' while True: try: fd = select.select([process.stdout], [], [])[0][0] except IndexError: continue # not ready / no further data except select.error as exc: if exc.args[0] == errno.EINTR: continue raise # get the next line line = fd.read() if not line: break # no more text from stdout unfinished_line = '' if line.endswith('\n') else '%s%s' % (unfinished_line, line.rsplit('\n', 1)[-1]) for line in line.splitlines(): parse(line) if unfinished_line: parse(unfinished_line) # get all remaining output stdout, stderr = process.communicate() if stderr: # write stderr into the log file MODULE.warn('stderr from univention-join: %s' % stderr) # check for errors if process.returncode != 0: # error case MODULE.warn('Could not perform system join: %s%s' % (stdout, stderr)) error_handler(_('Software packages have been installed successfully, however, the join process could not be executed. More details can be found in the log file /var/log/univention/join.log. Please retry to join the system via the UMC module "Domain join" after resolving any conflicting issues.')) elif failed_join_scripts: MODULE.warn('The following join scripts could not be executed: %s' % failed_join_scripts) error_handler(_('Software packages have been installed successfully, however, some join scripts could not be executed. More details can be found in the log file /var/log/univention/join.log. Please retry to execute the join scripts via the UMC module "Domain join" after resolving any conflicting issues.')) finally: # make sure that UMC servers and apache can be restarted again MODULE.info('enabling UMC and apache server restart') subprocess.call(CMD_ENABLE_EXEC)
def _thread(obj): obj._net_apply_running += 1 MODULE.process('Applying network settings') with util.written_profile(values): util.run_networkscrips(demo_mode)
def install(self, request): # get all arguments username = request.options.get('username') password = request.options.get('password') master = request.options.get('master') school_ou = request.options.get('schoolOU') educational_slave = request.options.get('nameEduServer') ou_display_name = request.options.get('OUdisplayname', school_ou) # use school OU name as fallback server_type = request.options.get('server_type') setup = request.options.get('setup') server_role = ucr.get('server/role') joined = os.path.exists('/var/univention-join/joined') if self._installation_started: raise ValueError('The installation was started twice. This should not have happened.') if server_role != 'domaincontroller_slave': # use the credentials of the currently authenticated user on a master/backup system self.require_password() username = self.username password = self.password master = '%s.%s' % (ucr.get('hostname'), ucr.get('domainname')) if server_role == 'domaincontroller_backup': master = ucr.get('ldap/master') self.original_certificate_file = None # check for valid school OU if ((setup == 'singlemaster' and server_role == 'domaincontroller_master') or server_role == 'domaincontroller_slave') and not RE_OU.match(school_ou): raise SchoolInstallerError(_('The specified school OU is not valid.')) # check for valid server role if server_role not in ('domaincontroller_master', 'domaincontroller_backup', 'domaincontroller_slave'): raise SchoolInstallerError(_('Invalid server role! UCS@school can only be installed on the system roles master domain controller, backup domain controller, or slave domain controller.')) if server_role == 'domaincontroller_slave' and not server_type: raise SchoolInstallerError(_('Server type has to be set for domain controller slave')) if server_role == 'domaincontroller_slave' and server_type == 'administrative' and not educational_slave: raise SchoolInstallerError(_('The name of an educational server has to be specified if the system shall be configured as administrative server.')) if server_role == 'domaincontroller_slave' and server_type == 'administrative' and educational_slave.lower() == ucr.get('hostname').lower(): raise SchoolInstallerError(_('The name of the educational server may not be equal to the name of the administrative slave.')) if server_role == 'domaincontroller_slave': # on slave systems, download the certificate from the master in order # to be able to build up secure connections self.original_certificate_file = self.retrieve_root_certificate(master) if server_role != 'domaincontroller_master': # check for a compatible environment on the DC master masterinfo = self._umc_master(username, password, master, 'schoolinstaller/get/metainfo') school_environment = masterinfo['school_environment'] master_samba_version = masterinfo['samba'] if not school_environment: raise SchoolInstallerError(_('Please install UCS@school on the master domain controller system. Cannot proceed installation on this system.')) if master_samba_version == 3: raise SchoolInstallerError(_('This UCS domain uses Samba 3 which is no longer supported by UCS@school. Please update all domain systems to samba 4 to be able to continue.')) if server_role == 'domaincontroller_slave' and school_environment != 'multiserver': raise SchoolInstallerError(_('The master domain controller is not configured for a UCS@school multi server environment. Cannot proceed installation on this system.')) if server_role == 'domaincontroller_backup' and school_environment != setup: raise SchoolInstallerError(_('The UCS@school master domain controller needs to be configured similarly to this backup system. Please choose the correct environment type for this system.')) if server_role == 'domaincontroller_backup' and not joined: raise SchoolInstallerError(_('In order to install UCS@school on a backup domain controller, the system needs to be joined first.')) # everything ok, try to acquire the lock for the package installation lock_aquired = self.package_manager.lock(raise_on_fail=False) if not lock_aquired: MODULE.warn('Could not aquire lock for package manager') raise SchoolInstallerError(_('Cannot get lock for installation process. Another package manager seems to block the operation.')) # see which packages we need to install MODULE.process('performing UCS@school installation') packages_to_install = [] installed_samba_version = self.get_samba_version() if installed_samba_version == 3: raise SchoolInstallerError(_('This UCS domain uses Samba 3 which is no longer supported by UCS@school. Please update all domain systems to samba 4 to be able to continue.')) if server_role == 'domaincontroller_slave': # slave packages_to_install.extend(['univention-samba4', 'univention-s4-connector']) if server_type == 'educational': packages_to_install.append('ucs-school-slave') else: packages_to_install.append('ucs-school-nonedu-slave') else: # master or backup if setup == 'singlemaster': if installed_samba_version: pass # do not install samba a second time else: # otherwise install samba4 packages_to_install.extend(['univention-samba4', 'univention-s4-connector']) packages_to_install.append('ucs-school-singlemaster') elif setup == 'multiserver': packages_to_install.append('ucs-school-master') else: raise SchoolInstallerError(_('Invalid UCS@school configuration.')) MODULE.info('Packages to be installed: %s' % ', '.join(packages_to_install)) # reset the current installation progress steps = 100 # installation -> 100 if server_role != 'domaincontroller_backup' and not (server_role == 'domaincontroller_master' and setup == 'multiserver'): steps += 10 # create_ou -> 10 if server_role == 'domaincontroller_slave': steps += 10 # move_slave_into_ou -> 10 steps += 100 # system_join -> 100 steps self._installation_started = True progress_state = self.progress_state progress_state.reset(steps) progress_state.component = _('Installation of UCS@school packages') self.package_manager.reset_status() def _thread(_self, packages): MODULE.process('Starting package installation') with _self.package_manager.locked(reset_status=True, set_finished=True): with _self.package_manager.no_umc_restart(exclude_apache=True): _self.package_manager.update() if not _self.package_manager.install(*packages): raise SchoolInstallerError(_('Failed to install packages.')) if server_role != 'domaincontroller_backup' and not (server_role == 'domaincontroller_master' and setup == 'multiserver'): # create the school OU (not on backup and not on master w/multi server environment) MODULE.info('Starting creation of LDAP school OU structure...') progress_state.component = _('Creation of LDAP school structure') progress_state.info = '' try: if server_role == 'domaincontroller_slave': _educational_slave = ucr.get('hostname') if server_type == 'educational' else educational_slave administrative_slave = None if server_type == 'educational' else ucr.get('hostname') create_ou_remote(master, username, password, school_ou, ou_display_name, _educational_slave, administrative_slave) elif server_role == 'domaincontroller_master': create_ou_local(school_ou, ou_display_name) except SchoolInstallerError as exc: MODULE.error(str(exc)) raise SchoolInstallerError(_( 'The UCS@school software packages have been installed, however, a school OU could not be created and consequently a re-join of the system has not been performed. ' 'Please create a new school OU structure using the UMC module "Add school" on the master and perform a domain join on this machine via the UMC module "Domain join".' )) progress_state.add_steps(10) if server_role == 'domaincontroller_slave': # make sure that the slave is correctly moved below its OU MODULE.info('Trying to move the slave entry in the right OU structure...') result = umc(username, password, master, 'schoolwizards/schools/move_dc', {'schooldc': ucr.get('hostname'), 'schoolou': school_ou}, 'schoolwizards/schools').result if not result.get('success'): MODULE.warn('Could not successfully move the slave DC into its correct OU structure:\n%s' % result.get('message')) raise SchoolInstallerError(_('Validating the LDAP school OU structure failed. It seems that the current slave system has already been assigned to a different school or that the specified school OU name is already in use.')) # system join on a slave system progress_state.component = _('Domain join') if server_role == 'domaincontroller_slave': progress_state.info = _('Preparing domain join...') MODULE.process('Starting system join...') else: # run join scripts on DC backup/master progress_state.info = _('Executing join scripts...') MODULE.process('Running join scripts...') system_join( username, password, info_handler=self.progress_state.info_handler, step_handler=self.progress_state.add_steps, error_handler=self.progress_state.error_handler, ) def _finished(thread, result): MODULE.info('Finished installation') progress_state.finish() progress_state.info = _('finished...') self._installation_started = None if isinstance(result, SchoolInstallerError): MODULE.warn('Error during installation: %s' % (result,)) self.restore_original_certificate() progress_state.error_handler(str(result)) elif isinstance(result, BaseException): self.restore_original_certificate() msg = ''.join(traceback.format_exception(*thread.exc_info)) MODULE.error('Exception during installation: %s' % msg) progress_state.error_handler(_('An unexpected error occurred during installation: %s') % result) thread = notifier.threads.Simple('ucsschool-install', notifier.Callback(_thread, self, packages_to_install), notifier.Callback(_finished)) thread.run() self.finished(request.id, None)
def get_all_backups(lo, ucr=None): MODULE.process('Searching DC Backup') return [ host.info['fqdn'] for host in get_hosts(domaincontroller_backup, lo, ucr) ]
def auto_complete_values_for_join(newValues, current_locale=None): # try to automatically determine the domain, except on a dcmaster and basesystem if newValues[ 'server/role'] != 'domaincontroller_master' and not newValues.get( 'domainname') and newValues['server/role'] != 'basesystem': ucr.load() for nameserver in ('nameserver1', 'nameserver2', 'nameserver3'): if newValues.get('domainname'): break newValues['domainname'] = get_ucs_domain( newValues.get(nameserver, ucr.get(nameserver))) if not newValues['domainname']: raise Exception( _('Cannot automatically determine the domain. Please specify the server\'s fully qualified domain name.' )) # The check "and 'domainname' in newValues" is solely for basesystems isAdMember = 'ad/member' in newValues and 'ad/address' in newValues if 'windows/domain' not in newValues: if isAdMember: MODULE.process('Searching for NETBIOS domain in AD') for nameserver in ('nameserver1', 'nameserver2', 'nameserver3'): ns = newValues.get(nameserver, ucr.get(nameserver)) if ns: try: ad_domain_info = lookup_adds_dc( newValues.get('ad/address'), ucr={'nameserver1': ns}) except failedADConnect: pass else: newValues['windows/domain'] = ad_domain_info[ 'Netbios Domain'] MODULE.process( 'Setting NETBIOS domain to AD value: %s' % newValues['windows/domain']) break if 'windows/domain' not in newValues and 'domainname' in newValues: newValues['windows/domain'] = domain2windowdomain( newValues.get('domainname')) MODULE.process('Setting NETBIOS domain to default: %s' % newValues['windows/domain']) # make sure that AD connector package is installed if AD member mode is chosen selectedComponents = set(newValues.get('components', [])) if isAdMember and newValues['server/role'] == 'domaincontroller_master': selectedComponents.add('univention-ad-connector') # make sure to install the memberof overlay if it is installed on the DC Master if newValues['server/role'] not in ('domaincontroller_master', 'basesystem', 'memberserver'): if newValues.pop('install_memberof_overlay', None): selectedComponents.add('univention-ldap-overlay-memberof') # add lists with all packages that should be removed/installed on the system if selectedComponents: currentComponents = set() for iapp in get_apps(): if iapp['is_installed']: for ipackages in (iapp['default_packages'], iapp['default_packages_master']): currentComponents = currentComponents.union(ipackages) # set of all available software packages allComponents = set(['univention-ldap-overlay-memberof']) for iapp in get_apps(): for ipackages in (iapp['default_packages'], iapp['default_packages_master']): allComponents = allComponents.union(ipackages) # get all packages that shall be removed removeComponents = list(allComponents & (currentComponents - selectedComponents)) newValues['packages_remove'] = ' '.join(removeComponents) # get all packages that shall be installed installComponents = list(allComponents & (selectedComponents - currentComponents)) newValues['packages_install'] = ' '.join(installComponents) current_locale = Locale(ucr.get('locale/default', 'en_US.UTF-8:UTF-8')) if newValues['server/role'] == 'domaincontroller_master': # add newValues for SSL UCR variables default_locale = current_locale if 'locale/default' in newValues: default_locale = Locale(newValues['locale/default']) newValues['ssl/state'] = default_locale.territory newValues['ssl/locality'] = default_locale.territory newValues['ssl/organization'] = newValues.get('organization', default_locale.territory) newValues['ssl/organizationalunit'] = 'Univention Corporate Server' newValues['ssl/email'] = 'ssl@{domainname}'.format(**newValues) # make sure that the locale of the current session is also supported # ... otherwise the setup scripts will fail after regenerating the # locale data (in 20_language/10language) with some strange python # exceptions about unsupported locale strings... if 'locale' not in newValues: newValues['locale'] = newValues.get('locale/default', '') forcedLocales = ['en_US.UTF-8:UTF-8', 'de_DE.UTF-8:UTF-8' ] # we need en_US and de_DE locale as default language if current_locale: current_locale = '{0}:{1}'.format(str(current_locale), current_locale.codeset) forcedLocales.append(current_locale) for ilocale in forcedLocales: if ilocale not in newValues['locale']: newValues['locale'] = '%s %s' % (newValues['locale'], ilocale) return newValues
def auth_type(self, auth_type): MODULE.process('Setting auth type to %r' % (auth_type, )) self.__auth_type = auth_type
def invoke(self, request): # ATTENTION!!!!!!! # this function has to stay compatible with the very first App Center installations (Dec 2012) # if you add new arguments that change the behavior # you should add a new method (see invoke_dry_run) or add a function name (e.g. install-schema) # this is necessary because newer app center may talk remotely with older one # that does not understand new arguments and behaves the old way (in case of # dry_run: install application although they were asked to dry_run) host = request.options.get('host') function = request.options.get('function') send_as = function if function.startswith('install'): function = 'install' if function.startswith('update'): function = 'upgrade' if function == 'uninstall': function = 'remove' app_id = request.options.get('application') app = Apps().find(app_id) if app is None: raise umcm.UMC_Error(_('Could not find an application for %s') % (app_id,)) force = request.options.get('force') values = request.options.get('values') only_dry_run = request.options.get('only_dry_run') dont_remote_install = request.options.get('dont_remote_install') only_master_packages = send_as.endswith('schema') MODULE.process('Try to %s (%s) %s on %s. Force? %r. Only master packages? %r. Prevent installation on other systems? %r. Only dry run? %r.' % (function, send_as, app_id, host, force, only_master_packages, dont_remote_install, only_dry_run)) # REMOTE invocation! if host and host != self.ucr.get('hostname'): try: client = Client(host, self.username, self.password) result = client.umc_command('appcenter/invoke', request.options).result except (ConnectionError, HTTPError) as exc: MODULE.error('Error during remote appcenter/invoke: %s' % (exc,)) result = { 'unreachable': [host], 'master_unreachable': True, 'serious_problems': True, 'software_changes_computed': True, # not really... } else: if result['can_continue']: def _thread_remote(_client): with self.is_working(): self._query_remote_progress(_client) def _finished_remote(thread, result): if isinstance(result, BaseException): MODULE.warn('Exception during %s %s: %s' % (function, app_id, str(result))) thread = notifier.threads.Simple('invoke', notifier.Callback(_thread_remote, client), _finished_remote) thread.run() self.finished(request.id, result) return # make sure that the application can be installed/updated action = get_action(function)() args = action._build_namespace(app=app, username=self.username, password=self.password, noninteractive=True, skip_checks=['shall_have_enough_ram', 'shall_only_be_installed_in_ad_env_with_password_service', 'must_not_have_concurrent_operation'], send_info=not only_master_packages, set_vars=values, dry_run=True, install_master_packages_remotely=False, only_master_packages=only_master_packages) can_continue = True delayed_can_continue = True serious_problems = False result = { 'install': [], 'remove': [], 'broken': [], 'unreachable': [], 'master_unreachable': False, 'serious_problems': False, 'hosts_info': {}, 'problems_with_hosts': False, 'serious_problems_with_hosts': False, 'invokation_forbidden_details': {}, 'invokation_warning_details': {}, 'software_changes_computed': False, } if not app: MODULE.process('Application not found: %s' % app_id) can_continue = False if can_continue and not only_master_packages: if function == 'upgrade': app = Apps().find_candidate(app) if app is None: forbidden, warnings = {'must_have_candidate': False}, {} else: forbidden, warnings = app.check(function) if forbidden: MODULE.process('Cannot %s %s: %r' % (function, app_id, forbidden)) result['invokation_forbidden_details'] = forbidden can_continue = False serious_problems = True if warnings: MODULE.process('Warning trying to %s %s: %r' % (function, app_id, forbidden)) result['invokation_warning_details'] = warnings if not force: # don't stop "immediately". # compute the package changes! delayed_can_continue = False result['serious_problems'] = serious_problems result['can_continue'] = can_continue if can_continue: with self.locked(): if can_continue and function in ('install', 'upgrade'): result.update(self._install_dry_run_remote(app, function, dont_remote_install, force)) serious_problems = bool(result['master_unreachable'] or result['serious_problems_with_hosts']) if serious_problems: args.dry_run = True result.update(action.dry_run(app, args)) result['software_changes_computed'] = True serious_problems = bool(result['broken'] or serious_problems) if serious_problems or (not force and (result['unreachable'] or result['install'] or result['remove'] or result['problems_with_hosts'])): can_continue = False elif can_continue and function in ('remove',) and not force: result.update(action.dry_run(app, args)) result['software_changes_computed'] = True can_continue = False can_continue = can_continue and delayed_can_continue and not only_dry_run result['serious_problems'] = serious_problems result['can_continue'] = can_continue if can_continue and not only_dry_run: def _thread(module, app, function): with module.is_working(): if not dont_remote_install and function != 'remove': self._install_master_packages_on_hosts(app, function) with module.package_manager.no_umc_restart(exclude_apache=True): try: args.dry_run = False args.install_master_packages_remotely = False return action.call_with_namespace(args) except AppCenterError as exc: raise umcm.UMC_Error(str(exc), result=dict( display_feedback=True, title='%s %s' % (exc.title, exc.info))) def _finished(thread, result): if isinstance(result, BaseException): MODULE.warn('Exception during %s %s: %s' % (function, app_id, str(result))) thread = notifier.threads.Simple('invoke', notifier.Callback(_thread, self, app, function), _finished) thread.run() self.finished(request.id, result)
def adconnector_save(self, request): """Saves the Active Directory connection configuration options: Host_IP: IP address of the AD server LDAP_Host: hostname of the AD server LDAP_Base: LDAP base of the AD server LDAP_BindDN: LDAP DN to use for authentication KerberosDomain: kerberos domain PollSleep: time in seconds between polls RetryRejected: how many time to retry a synchronisation MappingSyncMode: synchronisation mode MappingGroupLanguage: language of the AD server return: { 'success' : (True|False), 'message' : <details> } """ for umckey, ucrkey, default in Instance.OPTION_MAPPING: val = request.options.get(umckey, default) if val: if isinstance(val, bool): val = 'yes' if val else 'no' MODULE.info('Setting %s=%s' % (ucrkey, val)) univention.config_registry.handler_set([u'%s=%s' % (ucrkey, val)]) ucr.load() if ucr.get('connector/ad/ldap/ldaps'): MODULE.info('Unsetting connector/ad/ldap/ldaps') univention.config_registry.handler_unset([u'connector/ad/ldap/ldaps']) if ucr.get('connector/ad/ldap/port') == '636': MODULE.info('Setting ldap port to 389') univention.config_registry.handler_set([u'connector/ad/ldap/port=389']) if not request.options.get('LDAP_Password') in (None, '', DO_NOT_CHANGE_PWD): fn = ucr.get('connector/ad/ldap/bindpw', FN_BINDPW) try: with open(fn, 'w') as fd: fd.write(request.options.get('LDAP_Password')) os.chmod(fn, 0o600) os.chown(fn, 0, 0) univention.config_registry.handler_set([u'connector/ad/ldap/bindpw=%s' % fn]) except Exception as e: MODULE.info('Saving bind password failed (filename=%(fn)s ; exception=%(exception)s)' % {'fn': fn, 'exception': str(e.__class__)}) self.finished(request.id, {'success': False, 'message': _('Saving bind password failed (filename=%(fn)s ; exception=%(exception)s)') % {'fn': fn, 'exception': str(e.__class__)}}) return ssldir = '/etc/univention/ssl/%s' % request.options.get('LDAP_Host') if not os.path.exists(ssldir): self._create_certificate(request) return # enter a static host entry such that the AD server's FQDN can be resolved univention.config_registry.handler_set([u'hosts/static/%(Host_IP)s=%(LDAP_Host)s' % request.options]) # check for SSL support on AD side if admember.server_supports_ssl(server=request.options.get('LDAP_Host')): MODULE.process('Enabling SSL...') admember.enable_ssl() else: MODULE.warn('SSL is not supported') admember.disable_ssl() # UCR variables are set, and now we can try to guess the language of # the AD domain ad_lang = guess_ad_domain_language() univention.config_registry.handler_set([u'connector/ad/mapping/group/language=%s' % ad_lang]) self.finished(request.id, {'success': True, 'message': _('Active Directory connection settings have been saved.')})
def get_master(lo): MODULE.process('Searching Primary Directory Node') return get_hosts(domaincontroller_master, lo)[0].info['fqdn']