ACC SHELL

Path : /proc/self/root/usr/share/YaST2/modules/
File Upload :
Current File : //proc/self/root/usr/share/YaST2/modules/Ldap.ycp

/**
 * File:	modules/Ldap.ycp
 * Module:	Configuration of LDAP client
 * Summary:	LDAP client configuration data, I/O functions.
 * Authors:	Thorsten Kukuk <kukuk@suse.de>
 *		Anas Nashif <nashif@suse.de>
 *
 * $Id: Ldap.ycp 61773 2010-04-20 09:54:37Z jsuchome $
 */

{

    module "Ldap";
    textdomain "ldap-client";

    import "Autologin";
    import "Directory";
    import "FileUtils";
    import "DNS";
    import "Hostname";
    import "Label";
    import "Message";
    import "Mode";
    import "Nsswitch";
    import "Package";
    import "Pam";
    import "Popup";
    import "ProductFeatures";
    import "Progress";
    import "Report";
    import "Service";
    import "Stage";
    import "Summary";

    /**
     * show popups with error messages?
     */
    global boolean use_gui	= true;

    /**
     * DN of base configuration object
     */
    global string base_config_dn = "";


    include "ldap/routines.ycp";

    /**
     * Required packages for this module to operate
     * -- they are now required only when LDAP is set for authentication
     */
    global list<string> required_packages = [];

    /**
     * Write only, used during autoinstallation.
     * Don't run services and SuSEconfig, it's all done at one place.
     */
    global boolean write_only = false;

    /**
     * Are LDAP services available via nsswitch.conf?
     */
    global boolean start	= false;
    global boolean old_start	= false;

    /**
     * Is NIS service available? If yes, and LDAP client will be enabled, warn
     * user (see bug #36981)
     */
    global boolean nis_available	= false;

    /**
     * If no, automounter will not be affected.
     */
    global boolean _autofs_allowed = true;
    /**
     * Start automounter and import the settings from LDAP
     */
    global boolean _start_autofs = false;

    /**
     * If login of LDAP uses to local machine is enabled
     */
    global boolean login_enabled = true;

    /**
     * which attribute have LDAP groups for list of members
     */
    global string member_attribute	= "";
    global string old_member_attribute	= "";

    /**
     * IP addresses of LDAP server.
     */
    global string  server = "";
    global string  old_server = "";

    // local settings modified?
    global boolean modified = false;

    // /etc/openldap/ldap.conf modified?
    global boolean openldap_modified = false;

    // base DN
    string base_dn		= "";
    string old_base_dn		= nil;
    boolean base_dn_changed	= false;

    /* Do we have an v2 or v3 ldap server? */
    global boolean ldap_v2 = false;
    global boolean ldap_tls = false;

    // CA certificates for server certificate verification
    // At least one of these are required if tls_checkpeer is "yes"
    global string tls_cacertdir		= "";
    global string tls_cacertfile	= "";
    // Require and verify server certificate (yes/no)
    global string tls_checkpeer		= "yes";

    // Which crypt method should be used?
    global string pam_password = "exop";

    // lines of /etc/passwd, starting with +/-
    global list<string> plus_lines_passwd	= [];

    global integer default_port = 389;

    /**
     * If home directories of LDAP users are stored on this machine
     */
    global boolean file_server = false;

    // settings from ldap.conf
    global string nss_base_passwd = "";
    global string nss_base_shadow = "";
    global string nss_base_group = "";
    // settings from LDAP configuration objects
    global string user_base = "";
    global string group_base = "";

    /* stored values of /etc/nsswitch.conf */
    map nsswitch = $[
	"passwd" : [], "group": [], "passwd_compat": [], "group_compat": []
    ];

    // are we binding anonymously?
    global boolean anonymous = false;

    // bind password for LDAP server
    global string bind_pass = nil;

    /**
     * DN for binding to LDAP server
     */
    global string bind_dn = "";

    // DN of currently edited configuration module
    global string current_module_dn = "";
    // DN of currently edited template
    global string current_template_dn = "";

    // if LDAP configuration objects should be created automaticaly
    global boolean create_ldap	= false;

    // if eDirectory is used as server
    global boolean nds		= false;

    // if crypted connection was switched of after failure (#330054)
    global boolean tls_switched_off = false;

    boolean nds_checked		= false;

    // if OES is used as a client
    boolean oes			= false;

    // expert ui means "server product"
    boolean expert_ui		= false;

    /**
     * defaults for adding new config objects and templates
     */
    global map new_objects = $[
	"suseUserConfiguration"	: $[
	    "suseSearchFilter"	: ["objectClass=posixAccount"],
	    "susePasswordHash"	: ["SSHA"],
	    "suseSkelDir"	: ["/etc/skel"],
	    "suseMinUniqueId"	: ["1000"],
	    "suseNextUniqueId"	: ["1000"],
	    "suseMaxUniqueId"	: ["60000"],
	    "suseMinPasswordLength" : ["5"],
	    "suseMaxPasswordLength" : ["8"],
	],
	"suseGroupConfiguration": $[
	    "suseSearchFilter"	: ["objectClass=posixGroup"],
	    "suseMinUniqueId"	: ["1000"],
	    "suseNextUniqueId"	: ["1000"],
	    "suseMaxUniqueId"	: ["60000"],
	],
	"suseUserTemplate"	: $[
	    "objectClass"	: [
		"top", "suseObjectTemplate", "suseUserTemplate"
	    ],
	    "suseNamingAttribute"	: [ "uid" ],
	    "suseDefaultValue"  : [
		"homeDirectory=/home/%uid",
		"loginShell=/bin/bash"
	    ],
	    "susePlugin"		: [ "UsersPluginLDAPAll" ]
	],
	"suseGroupTemplate"	: $[
	    "objectClass"	: [
		"top", "suseObjectTemplate", "suseGroupTemplate"
	    ],
	    "suseNamingAttribute"	: [ "cn" ],
	    "susePlugin"		: [ "UsersPluginLDAPAll" ]
	]
    ];

    global string base_template_dn = base_config_dn;

    // settings saved at LDAP server modified
    global boolean ldap_modified = false;

    global map config_modules = $[];
    global map templates = $[];

    global boolean bound = false;

    // DN's of groups (posixGroups) in LDAP
    global list groups_dn = [];

    /**
     * Map of object classes (from schema). Indexed by names.
     */
    global map object_classes = $[];

    /**
     * Map of atribute types (from schema). Indexed by names.
     */
    global map attr_types = $[
    ];

    /**
     * encryption schemes supported by slappasswd
     */
    global list hash_schemas = ["CLEAR", "CRYPT", "SHA", "SSHA", "MD5", "SMD5"];

    /**
     * Available configuration modules (objectClass names)
     * TODO update
     */
    global list<string> available_config_modules = [
	"suseUserConfiguration",
	"suseGroupConfiguration"
    ];

    /**
     * The defualt values, which should replace the ones from Read ()
     * Used during instalation, when we want to do a reasonable proposal
     */
    global map initial_defaults = $[];

    /**
     * If the default values, used from ldap-server module were used
     * to configure ldap-client
     */
    global boolean initial_defaults_used = false;

    global boolean schema_initialized	= false;

    global boolean ldap_initialized	= false;

    /**
     * If false, do not read settings already set from outside
     * used e.g. for Users YaPI. see bug #60898
     */
    global boolean read_settings	= true;

    /**
     * if sshd should be restarted during write phase
     */
    global boolean restart_sshd		= false;

    // if /etc/passwd was read
    boolean passwd_read			= false;

    /**
     * if pam_mkhomedir is set in /etc/pam.d/commond-session
     */
    global boolean mkhomedir        = false;

    // map with modifications of Password Policies objects
    global map<string,map> ppolicies	= $[];

    //----------------------------------------------------------------

    /**
     * If the base DN has changed from a nonempty one, it may only be
     * changed at boot time. Use this to warn the user.
     * @return whether changed by SetBaseDN
     */
    global define boolean BaseDNChanged () {
	return base_dn_changed;
    }

    // obsolete, use BaseDNChanged
    global define boolean DomainChanged () ``{
	return BaseDNChanged ();
    }

    /**
     * Get the Base DN
     */
    global define string GetBaseDN () {
	return base_dn;
    }

    // obsolete, use GetBaseDN
    global define string GetDomain () ``{
	return GetBaseDN ();
    }

    /**
     * Set new LDAP base DN
     * @param new_base_dn a new base DN
     */
    global define void SetBaseDN (string new_base_dn) {
	base_dn = new_base_dn;
	if (base_dn != old_base_dn && old_base_dn != "")
	{
	    base_dn_changed = true;
	}
    }

    // obsolete, use SetBaseDN
    global define void SetDomain (string new_domain) {
	return SetBaseDN (new_domain);
    }

    /**
     * Set the defualt values, which should replace the ones from Read ()
     * Used during instalation, when we want to do a reasonable proposal
     */
    global define boolean SetDefaults (map settings) ``{

	y2milestone ("using initial defaults: %1", settings);
	initial_defaults = eval (settings);
	return true;
    }

    /**
     * set the value of read_settings variable
     * which means, do not read some settings from system
     */
    global define boolean SetReadSettings (boolean read) {

	read_settings	= read;
	return read_settings;
    }

    /**
     * Return needed packages and packages to be removed
     * during autoinstallation.
     * @return map of lists.
     */
    global  define map AutoPackages() ``{

      if (start)
	required_packages = (list<string>)
	    union (required_packages, ["pam_ldap", "nss_ldap"]);
      return ($["install": required_packages, "remove": []]);
    }


    /* ------------- auto_yast functions -------------------------------- */

    /**
     * Only set variables, without checking anything
     * @return: void
     */
    global define void Set (map settings)  ``{
	start		= settings ["start_ldap"]:false;
	server		= settings ["ldap_server"]:"";
	// leaving "ldap_domain" for backward compatibility
	base_dn		= settings ["ldap_domain"]:"";
	ldap_v2		= settings ["ldap_v2"]:false;
	ldap_tls	= settings ["ldap_tls"]:false;
	pam_password	= settings ["pam_password"]:"exop";
	bind_dn		= settings ["bind_dn"]:"";
	file_server	= settings ["file_server"]:false;
	base_config_dn	= settings ["base_config_dn"]:"";
        nss_base_passwd = settings ["nss_base_passwd"]:"";
        nss_base_shadow = settings ["nss_base_passwd"]:"";
        nss_base_group	= settings ["nss_base_group"]:"";
	member_attribute= settings ["member_attribute"]:"member";
	create_ldap	= settings ["create_ldap"]:false;
	login_enabled	= settings ["login_enabled"]:true;
	_start_autofs	= settings ["start_autofs"]:false;
	tls_cacertdir	= settings ["tls_cacertdir"]:"";
	tls_cacertfile	= settings ["tls_cacertfile"]:"";
	tls_checkpeer	= settings ["tls_checkpeer"]:"yes";
	mkhomedir	= settings ["mkhomedir"]:mkhomedir;
	if (_start_autofs)
	    required_packages = (list<string>) union (required_packages, ["autofs"]);

	old_base_dn		= base_dn;
	old_server		= server;
	old_member_attribute	= member_attribute;
	modified		= true;
	openldap_modified	= true;
	return;
    }

    /**
     * Get all the LDAP configuration from a map.
     * When called by ldap_auto (preparing autoinstallation data)
     * the map may be empty.
     * @param settings	$["start": "domain": "servers":[...] ]
     * @return	success
     */
    global define boolean Import (map settings) ``{
	Set (settings);
	return true;
    }

    /**
     * Dump the LDAP settings to a map, for autoinstallation use.
     * @return $["start":, "servers":[...], "domain":]
     */
    global define map Export () ``{
	map e = $[
	    "start_ldap"	: start,
	    "ldap_server"	: server,
	    "ldap_domain"	: base_dn,
	    "ldap_v2"		: ldap_v2,
	    "ldap_tls"		: ldap_tls,
	    "bind_dn"		: bind_dn,
	    "file_server"	: file_server,
	    "base_config_dn"	: base_config_dn,
	    "pam_password"	: pam_password,
	    "member_attribute"	: member_attribute,
	    "create_ldap"	: create_ldap,
	    "login_enabled"	: login_enabled,
	    "mkhomedir"		: mkhomedir
	];
	if (tls_checkpeer != "yes")
	    e["tls_checkpeer"]	= tls_checkpeer;
	if (tls_cacertdir != "")
	    e["tls_cacertdir"]	= tls_cacertdir;
	if (tls_cacertfile != "")
	    e["tls_cacertfile"]	= tls_cacertfile;
	if (nss_base_passwd != base_dn)
	    e["nss_base_passwd"]    = nss_base_passwd;
	if (nss_base_shadow != base_dn)
	    e["nss_base_shadow"]    = nss_base_shadow;
	if (nss_base_group != base_dn)
	    e["nss_base_group"]     = nss_base_group;
	if (_autofs_allowed)
	    e["start_autofs"]	= _start_autofs;
	return e;
    }

    /**
     * Summary()
     * returns html formated configuration summary
     * @return summary
     */
    global define string Summary ()
	``{
	string summary = "";
	// summary item
	summary = Summary::AddHeader(summary, _("LDAP Client Enabled"));
	// summary (is LDAP enabled?)
	summary = Summary::AddLine(summary, (start) ? _("Yes") : Summary::NotConfigured());
	// summary item
	summary = Summary::AddHeader(summary, _("LDAP Domain"));
	summary = Summary::AddLine(summary, (base_dn != "") ? base_dn : Summary::NotConfigured());
	// summary item
	summary = Summary::AddHeader(summary, _("LDAP Server"));
	summary = Summary::AddLine(summary,( server!="") ? server : Summary::NotConfigured());
	// summary item
	summary = Summary::AddHeader(summary, _("LDAP Version 2"));
	// summary (LDAP version 2?)
	summary = Summary::AddLine(summary, (ldap_v2) ? _("Yes") : Summary::NotConfigured());
	// summary item
	summary = Summary::AddHeader(summary, _("LDAP TLS/SSL"));
	// summary (use TLS?)
	summary = Summary::AddLine(summary, (ldap_tls) ? _("Yes") : Summary::NotConfigured());

	return summary;
    }

    /**
     * returns html formated configuration summary (shorter than Summary)
     * @return summary
     */
    global define string ShortSummary () ``{
	string nc = Summary::NotConfigured();
	string summary = "";
	// summary text
	summary = sformat (_("<b>Servers</b>:%1<br>"), server!=""? server: nc) +
	// summary text
	sformat (_("<b>Base DN</b>:%1<br>"), base_dn != "" ? base_dn : nc) +
	// summary text (yes/no follows)
	sformat (_("<b>Client Enabled</b>:%1"), start ?
	    // summary (client enabled?)
	    _("Yes") :
	    // summary (client enabled?)
	    _("No"));
	if (_start_autofs)
	    // summary
	    summary	= summary + "<br>" + _("Automounter Configured");
	if (ldap_tls)
	{
	    // summary
	    summary	= summary + "<br>" + _("LDAP TLS/SSL Configured");
	}
	return summary;
    }

    /* ------------- read/write functions ------------------------------- */

    /**
     * Read single entry from /etc/ldap.conf file
     * @param entry entry name
     * @param defvalue default value if entry is not present
     * @return entry value
     */
    define string ReadLdapConfEntry (string entry, string defvalue) ``{

	string value = defvalue;
	any ret = (any) SCR::Read(
	    add (.etc.ldap_conf.v."/etc/ldap.conf", entry));
	if (ret == nil)
	    value = defvalue;
	else if (is (ret,list))
	{
	    value = ((list)ret)[0]:defvalue;
	}
	else
	    value = sformat ("%1", ret);
	return value;
    }

    /**
     * Read multi-valued entry from /etc/ldap.conf file
     * @param entry entry name
     * @return entry value
     */
    define list<string> ReadLdapConfEntries (string entry) ``{

	any ret = (any) SCR::Read(
	    add (.etc.ldap_conf.v."/etc/ldap.conf", entry));
	if (ret == nil)
	    return [];
	else if (is (ret,list))
	{
	    return (list<string>) ret;
	}
	else
	    return [ sformat ("%1", ret) ];
    }

    /**
     * Write (single valued) entry to /etc/ldap.conf
     * @param entry name
     * @param value; if value is nil, entry will be removed
     */
    define void WriteLdapConfEntry (string entry, string value) ``{

	SCR::Write (add (.etc.ldap_conf.v."/etc/ldap.conf",entry),
	    value == nil ? nil : [value]
	);
    }

    /**
     * Write (possibly multi valued) entry to /etc/ldap.conf
     * @param entry name
     * @param value it is of type [attr1, attr2],
     * in /etc/ldap.conf should be written as "entry attr1 attr2"
     * @example to write "nss_map_attribute       uniquemember member", call
     * WriteLdapConfEntries ("nss_map_attribute", ["uniquemember", "member"])
     */
    define void WriteLdapConfEntries (string entry, list<string> value) ``{

	list<string> current	= ReadLdapConfEntries (entry);
	list<string> values	= [];
	foreach (string val, current, ``{
	    list lval	= splitstring (val, " \t");
	    if (tolower (lval[0]:"") != tolower (value[0]:""))
		values	= add (values, val);
	    else
		values	= add (values, mergestring (value, " "));
	});
	if (size (current) == 0)
	    values	= [ mergestring (value, " ") ];
	SCR::Write (add (.etc.ldap_conf.v."/etc/ldap.conf", entry), values);
    }

    /**
     * Add a new value to the entry in /etc/ldap.conf
     * @param entry name
     * @param value
     */
    define void AddLdapConfEntry (string entry, string value) ``{

	list<string> current	= ReadLdapConfEntries (entry);
	current = maplist (string e, current, ``(tolower (e)));

	if (!contains (current, tolower (value)))
	{
	    SCR::Write (add (.etc.ldap_conf.v."/etc/ldap.conf", entry),
		union (current, [value]));
	}
    }

    /**
     * Check if current machine runs OES
     */
    global define boolean CheckOES () {
	oes = Package::Installed ("NOVLam");
	return oes;
    }

    /**
     * Reads LDAP settings from the SCR
     * @return success
     */
    global define boolean Read () {

	expert_ui	=
	    ProductFeatures::GetFeature ("globals", "ui_mode") == "expert";

	CheckOES ();

	foreach (string db,["passwd","group","passwd_compat","group_compat"],``{
	    nsswitch[db]	= Nsswitch::ReadDb (db);
	});

	start = contains (nsswitch["passwd"]:[], "ldap")		||
		(contains (nsswitch["passwd"]:[], "compat") &&
		 contains (nsswitch["passwd_compat"]:[], "ldap"))	||
		(oes && contains (nsswitch["passwd"]:[], "nam"));

	old_start	= start;

	nis_available	= contains (nsswitch["passwd"]:[], "nis") ||
	    (contains (nsswitch["passwd"]:[], "compat") &&
	    (contains (nsswitch["passwd_compat"]:[], "nis") ||
	     size (nsswitch["passwd_compat"]:[]) == 0));
	nis_available	= nis_available && (Service::Status ("ypbind") == 0);

	server = ReadLdapConfEntry ("host", "");
	base_dn  = ReadLdapConfEntry ("base", "");

	old_base_dn = base_dn;
	old_server = server;

	// ask DNS for LDAP server address if none is defined
	if ((server == "" ||
	    (server == "127.0.0.1" && base_dn == "dc=example,dc=com"))
	    && FileUtils::Exists ("/usr/bin/dig") && !Mode::test())
	{
	    string domain	= Hostname::CurrentDomain ();
	    // workaround for bug#393951
	    if (domain == "" && Stage::cont ())
	    {
	        map out = (map) SCR::Execute (.target.bash_output,"domainname");
		if (out["exit"]:0 == 0)
		    domain = deletechars (out["stdout"]:"", "\n");
	    }
	    map out = (map) SCR::Execute (.target.bash_output, sformat ("dig SRV _ldap._tcp.%1 +short", domain));
	    string first = splitstring (out["stdout"]:"", "\n")[0]:"";
	    string srv	= splitstring (first, " ")[3]:"";
	    if (srv != "")
	    {
		// remove dot from the end of line
		server	= substring (srv, 0, size (srv) - 1);
		y2milestone ("LDAP server address acquired from DNS...");
		// now, check if there is reasonable 'default' DN
		string dn	= "";
		foreach (string part, splitstring (domain, "."), {
		    if (dn != "")
			dn	= dn + ",";
		    dn	= dn + "dc=" + part;
		});
		if (0 == SCR::Execute (.target.bash, sformat ("ldapsearch -x -h %1 -s base -b '' namingContexts | grep -i '^namingContexts: %2'", server, dn)))
		{
		    y2milestone ("proposing DN %1 based on %2", dn, domain);
		    base_dn	= dn;
		}
	    }
	}

	ldap_v2 = (ReadLdapConfEntry ("ldap_version", "3") == "2");
	ldap_tls = (ReadLdapConfEntry ("ssl", "no") == "start_tls");
	tls_cacertdir	= ReadLdapConfEntry ("tls_cacertdir", "");
	tls_cacertfile	= ReadLdapConfEntry ("tls_cacertfile", "");
	tls_checkpeer	= ReadLdapConfEntry ("tls_checkpeer", "yes");

        nss_base_passwd = ReadLdapConfEntry ("nss_base_passwd", base_dn);
        nss_base_shadow = ReadLdapConfEntry ("nss_base_shadow", base_dn);
        nss_base_group = ReadLdapConfEntry ("nss_base_group", base_dn);

	pam_password = ReadLdapConfEntry ("pam_password", "exop");
	// check if Password Modify extenstion is supported (bnc#546398, c#6)
	if (pam_password == "exop")
	{
	    if (0 == SCR::Execute (.target.bash, "ldapsearch -x -b '' -s base") && // LDAP server accessible
		0 != SCR::Execute (.target.bash,
		    "ldapsearch -x -b '' -s base supportedExtension | grep -i '^supportedExtension:[[:space:]]*1.3.6.1.4.1.4203.1.11.1'"))
	    {
		y2warning ("'exop' value not supported on server, using 'crypt'");
		pam_password	= "crypt";
	    }
	}

	// read sysconfig values
	base_config_dn = (string)SCR::Read (.sysconfig.ldap.BASE_CONFIG_DN);
	if (base_config_dn == nil)
	    base_config_dn = "";

	file_server = ((string)SCR::Read (.sysconfig.ldap.FILE_SERVER)== "yes");

	if (read_settings || bind_dn == "")
	{
	    bind_dn = (string)SCR::Read (.sysconfig.ldap.BIND_DN);
	}
	if (bind_dn == nil || bind_dn == "")
	    bind_dn = ReadLdapConfEntry ("binddn", "");

	if (read_settings || member_attribute == "")
	{
	    list<string> map_attrs = ReadLdapConfEntries ("nss_map_attribute");
	    foreach (string map_attr, map_attrs, {
		if (issubstring (tolower (map_attr), "uniquemember"))
		{
		    list<string> attr	= splitstring (map_attr, " \t");
		    if (tolower (attr[0]:"") == "uniquemember")
		    {
			member_attribute	= attr[1]:member_attribute;
			// LDAP needs to know correct attribute name
			if (member_attribute == "uniquemember")
			    member_attribute = "uniqueMember";
			old_member_attribute	= member_attribute;
		    }
		}
	    });
	}

	// install on demand
	_autofs_allowed = true;
	_start_autofs = _autofs_allowed && Service::Enabled ("autofs");

	// read /etc/passwd to check + line:
	if (!(boolean)SCR::Execute (.passwd.init,$["base_directory":"/etc"]))
	{
	    string error	= (string) SCR::Read (.passwd.error);
	    y2error ("error: %1", error);
	}
	else
	{
	    passwd_read		= true;
	    plus_lines_passwd	=
		(list<string>) SCR::Read (.passwd.passwd.pluslines);
	    foreach (string plus_line, plus_lines_passwd, ``{
		list<string> plus	= splitstring (plus_line, ":");
		if (plus[size (plus) -1]:"" == "/sbin/nologin")
		    login_enabled	= false;
	    });
	}

	mkhomedir	= Pam::Enabled ("mkhomedir");

	Autologin::Read ();

	// Now check if previous configuration of LDAP server didn't proposed
	// some better values:
	if (Stage::cont ())
	{
	    if (size (initial_defaults) > 0)
	    {
		y2milestone ("using values defined externaly");
		string old_s	= old_server;
		string old_d	= old_base_dn;
		string old_m	= old_member_attribute;
		Set (initial_defaults);
		old_server	= old_s;
		old_base_dn	= old_d;
		old_member_attribute	= old_m;
	    }
	}

	if (member_attribute == "")
	{
	    member_attribute	= "member";
	    modified		= true;
	}

	return true;
    }

    /* ------------- functions for work with LDAP tree contents ------------ */

    /**
     * Error popup for errors detected during LDAP operation
     * @param type error type: binding/reading/writing
     * @param detailed error message (from agent-ldap)
     */
    global define void LDAPErrorMessage (string type, string error) ``{

        map ldap_error = $[
	// error message:
	"initialize": _("\nThe server could be down or unreachable.
"),

	// error message:
	"missing_dn": _("\nThe value of DN is missing or invalid.
"),

	// error message:
	"at_not_found": _("\nAttribute type not found.
"),

	// error message:
	"oc_not_found": _("\nObject class not found.
"),
	];

        map error_type = $[
	    // error message, more specific description follows
	    "init": _("Connection to the LDAP server cannot be established."),
	    // error message, more specific description follows
	    "bind": _("A problem occurred while connecting to the LDAP server."),
	    // error message, more specific description follows
	    "read": _("A problem occurred while reading data from the LDAP server."),
	    // error message, more specific description follows
	    "users": _("There was a problem with writing LDAP users."),
	    // error message, more specific description follows
	    "groups": _("There was a problem with writing LDAP groups."),
	    // error message, more specific description follows
	    "write": _("There was a problem with writing data to the LDAP server."),
	    // error message, more specific description follows
	    "schema": _("A problem occurred while reading schema from the LDAP server."),
	];

	if (!use_gui || Mode::commandline ())
	{
	    y2error (error_type[type]:"Unknown LDAP error");
	    y2error (ldap_error[error]:error);
	    return;
	}

	if (error == nil) error = "YaST error?";

	UI::OpenDialog (`HBox(`HSpacing (0.5),
	    `VBox(
		`VSpacing (0.5),
		// label
		`Left(`Heading (Label::ErrorMsg())),
		// default error message
		`Label (error_type[type]:_("An unknown LDAP error occurred.")),
		`ReplacePoint (`id(`rp), `Empty()),
		`VSpacing (0.5),
		`Left(`CheckBox (`id(`details), `opt (`notify),
		    // checkbox label
		    _("&Show Details"), false)),
		`PushButton (`id(`ok), `opt(`key_F10,`default),
		    Label::OKButton())
	    ),
	    `HSpacing(0.5))
	);
	any ret = nil;
	if (error == "")
	    UI::ChangeWidget (`id(`details), `Enabled, false);
	do
	{
	    ret = UI::UserInput();
	    if (ret == `details)
	    {
		if ((boolean)UI::QueryWidget (`id(`details), `Value))
		    UI::ReplaceWidget (`id(`rp), `VBox (
			`Label (ldap_error[error]:error)));
		else
		    UI::ReplaceWidget (`id(`rp), `Empty());
	    }
	}
	while (ret != `ok && ret != `cancel);
	UI::CloseDialog();
    }

    /**
     * Reads and returns error map (=message + code) from agent
     */
    global define map LDAPErrorMap () ``{

        return ((map)SCR::Read(.ldap.error));
    }

    /**
     * Reads and returns error message from agent
     */
    global define string LDAPError () ``{

	map err_map = LDAPErrorMap ();
	string error	= err_map["msg"]:"";
	if (err_map["server_msg"]:"" != "")
	{
	    error = sformat ("%1\n%2", error, err_map["server_msg"]:"");
	}
	return error;
    }


    /**
     * return administrator's DN
     * if it was not read yet, read it now
     */
    global define string GetBindDN () {

	if (bind_pass == nil && size (bind_dn) == 0)
	{
	    y2milestone ("--- bind dn not read yet or empty, reading now");
	    bind_dn = (string)SCR::Read (.sysconfig.ldap.BIND_DN);
	    if (bind_dn == nil || bind_dn == "")
		bind_dn = ReadLdapConfEntry ("binddn", "");
	}
	return bind_dn;
    }


    // this is a hack
    global define string GetFirstServer (string servers) ``{

	if (bind_pass == nil && servers == "")
	{
	    y2milestone ("--- server not read yet or empty, reading now");
	    servers	= ReadLdapConfEntry ("host", "");
	}

	list l_servers = splitstring (servers, " \t");
	string srv = l_servers[0]:"";
	return splitstring(srv, ":")[0]:"";
    }

    // this is a hack
    global define integer GetFirstPort (string servers) ``{

	if (bind_pass == nil && servers == "")
	{
	    y2milestone ("--- server not read yet or empty, reading now");
	    servers	= ReadLdapConfEntry ("host", "");
	}

	list l_servers = splitstring (servers, " \t");
	string srv = l_servers[0]:"";
	if (!issubstring (srv, ":"))
	    return default_port;
	string s_port = substring (srv, search (srv, ":") + 1);
	if (s_port == "" || tointeger (s_port) == nil)
	    return default_port;
	else return tointeger (s_port);
    }

    /**
     * Initializes LDAP agent
     */
    global define string LDAPInit () ``{

	// FIXME what if we have more servers? -> choose dialog?
	string ret = "";
	map args = $[
	    "hostname":	GetFirstServer (server),
	    "port":	GetFirstPort (server),
	    "version":	ldap_v2 ? 2 : 3,
	    "use_tls":	ldap_tls ? "yes" : "no"
	];
	boolean init = (boolean) SCR::Execute (.ldap, args);
	if (init == nil)
	{
	    // error message
	    ret = _("Unknown error. Perhaps 'yast2-ldap' is not available.");
	}
	else
	{
	    ldap_initialized	= init;
	    if (!init)
	    {
		ret = LDAPError();
	    }
	}
	return ret;
    }

    /**
     * popup shown after failed connection: ask for retry withou TLS (see bug 246397)
     * @return true if user wants to retry without TLS
     */
    global define boolean ConnectWithoutTLS (map errmap) {

	UI::OpenDialog (`HBox(`HSpacing (0.5),
	    `VBox(
		`VSpacing (0.5),
		// label
		`Left (`Heading (Label::ErrorMsg())),
		// error message
		`Left (`Label (_("Connection to the LDAP server cannot be established."))),
		`ReplacePoint (`id(`rp), `Empty()),
		`VSpacing (0.2),
		`Left (`CheckBox (`id(`details), `opt (`notify),
		    // checkbox label
		    _("&Show Details"), false)),
		`VSpacing (),
		`Left (`Label (
		// question following error message (yes/no buttons follow)
_("A possible reason for the failed connection may be that your client is
configured for TLS/SSL but the server does not support it.

Retry connection without TLS/SSL?
"))),
		`HBox (
		    `PushButton (`id(`yes), `opt(`key_F10,`default), Label::YesButton()),
		    `PushButton (`id(`no),`opt(`key_F9), Label::NoButton())
		)
	    ),
	    `HSpacing(0.5))
	);
	any ret = nil;
	do
	{
	    ret = UI::UserInput();
	    if (ret == `details)
	    {
		if ((boolean)UI::QueryWidget (`id(`details), `Value))
		    UI::ReplaceWidget (`id(`rp), `VBox (
			`Label (errmap["msg"]:"")));
		else
		    UI::ReplaceWidget (`id(`rp), `Empty());
	    }
	}
	while (ret != `yes && ret != `no);
	UI::CloseDialog ();
	return ret == `yes;
    }

    /**
     * Initializes LDAP agent, offers to turn off TLS if it failed
     * @args arguments to use for initializaton (if empty, uses the current values)
     */
    global define string LDAPInitWithTLSCheck (map args) {

	string ret = "";
	if (args == $[])
	    args = $[
		"hostname"	: GetFirstServer (server),
		"port"		: GetFirstPort (server),
		"version"	: ldap_v2 ? 2 : 3,
		"use_tls"	: ldap_tls ? "yes" : "no"
	    ];
	boolean init = (boolean) SCR::Execute (.ldap, args);
	// error message
	string unknown	= _("Unknown error. Perhaps 'yast2-ldap' is not available.");
	if (init == nil)
	{
	    ret	= unknown;
	}
	else
	{
	    if (!init)
	    {
		map errmap	= Ldap::LDAPErrorMap ();
		if (args["use_tls"]:"" == "yes" &&
		    errmap["tls_error"]:false && ConnectWithoutTLS (errmap))
		{
		    args["use_tls"]	= false;
		    init = (boolean) SCR::Execute (.ldap, args);
		    if (init == nil)
			ret	= unknown;
		    else if (!init)
			ret	= LDAPError();
		    else
		    {
			y2milestone ("switching TLS off...");
			tls_switched_off	= true;
		    }
		}
		else
		{
		    ret	= errmap["msg"]:"";
		    if (errmap["server_msg"]:"" != "")
			ret = sformat ("%1\n%2", ret, errmap["server_msg"]:"");
		}
	    }
	    ldap_initialized	= init;
	}
	return ret;
    }

    /**
     * Binds to LDAP server
     * @param pass password
     */
    global define string LDAPBind (string pass) ``{

        string ret = "";
	if (pass != nil)
	{
	    map args = $[];
	    if (!anonymous)
		args = $[ "bind_dn": bind_dn, "bind_pw": pass];
	    if (!(boolean)SCR::Execute (.ldap.bind, args))
		ret = LDAPError();
	    else
		bound = true;
	}
	return ret;
    }

    /**
     * Asks user for bind password to LDAP server
     * @param anonymous if anonymous access could be allowed
     * @return password
     */
    global define string GetLDAPPassword (boolean enable_anonymous) ``{

	UI::OpenDialog (`opt(`decorated),
        `VBox(
            `HSpacing(40),
            // password entering label
            `Password(`id(`pw), `opt (`hstretch), _("&LDAP Server Password")),
	    // label
	    `Label (sformat (_("Server: %1:%2"),
		GetFirstServer (server), GetFirstPort (server))),
	    // label (%1 is admin DN - string)
	    `Label (sformat (_("Administrator: %1"), GetBindDN ())),
            `HBox(
              `PushButton (`id(`ok),`opt(`key_F10, `default),
		Label::OKButton()),
              // button label
	      `PushButton (`id(`anon), `opt(`key_F6), _("&Anonymous Access")),
              `PushButton (`id(`cancel),`opt(`key_F9), Label::CancelButton())
            ))
	);
	if (!enable_anonymous)
	    UI::ChangeWidget (`id(`anon), `Enabled, false);
	UI::SetFocus (`id(`pw));
	any ret = UI::UserInput();
	string pw = "";
	if (ret == `ok)
	{
	    pw = (string) UI::QueryWidget(`id(`pw), `Value);
	    anonymous = false;
	}
	else if (ret == `cancel)
	    pw = nil;
	else anonymous = true;
	UI::CloseDialog();
	return pw;
    }

    /**
     * Asks for LDAP password and tries to bind with it
     * @return password entered, nil on cancel
     */
    global define string LDAPAskAndBind (boolean enable_anonymous) ``{

	if (Mode::commandline ()) return nil;
	string pw = GetLDAPPassword (enable_anonymous);
        if (pw != nil)
	{
	    string ldap_msg = LDAPBind (pw);
	    while (pw != nil && ldap_msg != "")
	    {
		LDAPErrorMessage ("bind", ldap_msg);
		pw = GetLDAPPassword (enable_anonymous);
		ldap_msg = LDAPBind (pw);
	    }
	}
	return pw;
    }

    /**
     * Check if attribute allowes only single or multiple value
     * @param attr attribute name
     * @return answer
     */
    global define boolean SingleValued (string attr) ``{

	attr = tolower (attr);
	if (!haskey (attr_types, attr))
	{
	    map attr_type = (map)SCR::Read (.ldap.schema.at, $["name":attr]);
	    if (attr_type == nil)
		attr_type = $[];
	    attr_types [attr] = attr_type;
	}
	return attr_types [attr, "single"]:false;
    }

    /**
     * Gets the description of attribute (from schema)
     * @param attr attribute name
     * @return description
     */
    global define string AttributeDescription (string attr) ``{

	if (!haskey (attr_types, attr))
	{
	    map attr_type = (map)SCR::Read (.ldap.schema.at, $["name":attr]);
	    if (attr_type == nil)
		attr_type = $[];
	    attr_types [attr] = attr_type;
	}
	return attr_types [attr, "desc"]:"";
    }

    /**
     * Returns true if given object class exists in schema
     * @param class ObjectClass name
     */
    global define boolean ObjectClassExists (string class) ``{

	return (boolean)SCR::Read (.ldap.schema.oc.check, $["name":class]);
    }

    /**
     * Returns true if given object class is of 'structural' type
     * @param class ObjectClass name
     */
    global define boolean ObjectClassStructural (string class) ``{

	map object_class = (map)SCR::Read(.ldap.schema.oc, $["name":class]);
	return (object_class["kind"]:0 == 1);
    }


    /**
     * Returns allowed and required attributes of given object class
     * Read it from LDAP if it was not done yet.
     * @param string class name of object class
     * @return attribute names (list of strings)
     */
    global define list GetAllAttributes (string class) ``{

	class = tolower (class);
	if (!haskey (object_classes, class))
	{
	    map object_class = (map)SCR::Read(.ldap.schema.oc, $["name":class]);
	    if (object_class == nil)//TODO return from function?
		object_class = $[];
	    object_class ["all"] = union (object_class["may"]:[],
		object_class["must"]:[]);
	    // read attributes of superior classes
	    foreach (string sup_oc, object_class["sup"]:[], ``{
		list sup_all = GetAllAttributes (sup_oc);
		object_class ["all"] = union (object_class ["all"]:[], sup_all);
		object_class ["must"] = union (object_class ["must"]:[],
		    object_classes [sup_oc,"must"]:[]);
	    });
	    object_classes [class] = object_class;
	}
	return object_classes [class, "all"]:[];
    }

    /**
     * Returns required attributes of given object class
     * Read it from LDAP if it was not done yet.
     * @param string class name of object class
     * @return attribute names (list of strings)
     */
    global define list<string> GetRequiredAttributes (string class) ``{

	class = tolower (class);
	if (!haskey (object_classes, class))
	{
	    GetAllAttributes (class);
	}
	return object_classes [class, "must"]:[];
    }

    global define list<string> GetOptionalAttributes (string class) {

	class = tolower (class);
	if (!haskey (object_classes, class))
	{
	    GetAllAttributes (class);
	}
	return object_classes [class, "may"]:[];
    }

    /**
     * Returns the list of all allowed and required attributes for each
     * object class, given in the list of object classes
     * @param classes list of object classes whose attributes we want
     * @return attribute names (list of strings)
     */
    global define list GetObjectAttributes (list classes) ``{

	list ret = [];
	foreach (string class, (list<string>) classes, ``{
	    ret = union (ret, GetAllAttributes (class));
	});
	return ret;
    }

    /**
     * For a given object, add all atributes this object is allowed to have
     * according to its "objectClass" value. Added attributes have empty values.
     * @param object map describing LDAP entry
     * @return updated map
     */
    global define map AddMissingAttributes (map object) ``{

	foreach (string class, object["objectClass"]:[], {
	    foreach (string attr, (list<string>) GetAllAttributes (class), ``{
		if (!haskey (object, attr) && !haskey (object, tolower (attr)))
		    object = add (object, attr, []);
	    });
	});
	return object;
    }

    /**
     * Prepare agent for later schema queries
     * (agent reads schema to its internal structures)
     * @return error message
     */
    global define string InitSchema () ``{

	list schemas = (list)SCR::Read (.ldap.search, $[
	    "base_dn":  "",
	    "attrs":    [ "subschemaSubentry" ],
	    "scope":    0, //0:base
	]);
	string schema_dn = schemas[0,"subschemaSubentry",0]:"";
	if (schemas == nil || schema_dn == "")
	    return LDAPError();

	if (!(boolean)SCR::Execute (.ldap.schema, $[ "schema_dn": schema_dn ]))
	    return LDAPError();

	schema_initialized	= true;
	return "";
    }

    /**
     * In template object, convert the list of values
     * (which is in the form [ "a1=v1", "a2=v2"])
     * to map (in the form $[ "a1":"v1", "a2":"v2"]
     * @param templ original template map
     * @return updated template map
     */
    global define map ConvertDefaultValues (map templ) {

        map template = add (templ, "default_values", $[]);
	foreach (string value, templ["suseDefaultValue"]:[], {
	    list<string> lvalue	= splitstring (value, "=");
	    string at = lvalue[0]:"";
	    string v = size (lvalue) > 1 ?
		// '=' could be part of value, so we cannot use lvalue[1]
		substring (value, search (value, "=") + 1) : "";
	    template ["default_values", at] = v;
	});
	return template;
    }

    /**
     * Read object templates from LDAP server
     * @return string error message
     */
    global define string ReadTemplates () ``{

	templates = $[];
	map all = (map)SCR::Read (.ldap.search, $[
	    "base_dn":	base_config_dn,
	    "filter":   "objectClass=suseObjectTemplate",
	    "attrs":	[],
	    "scope":	2,// sub: all templates under config DN
	    "map":	true,
	    "not_found_ok":	true,
	]);
	if (all == nil)
	{
	    return LDAPError();
	}
	// create a helper map of default values inside ...
	templates = mapmap (string dn, map templ, (map<string, map<string,any> >)all, ``{
	    map template = ConvertDefaultValues (templ);
	    template = AddMissingAttributes (template);
	    return $[dn : template];
	});
	return "";
    }

    /**
     * Read configuration moduels from LDAP server
     * @return string error message
     */
    global define string ReadConfigModules () ``{

	config_modules = $[];
	map modules = (map)SCR::Read (.ldap.search, $[
	    "base_dn":	base_config_dn,
	    "filter":   "objectClass=suseModuleConfiguration",
	    "attrs":	[],
	    "scope":	1, // one - deeper searches would have problems with
			   // constructing the dn
	    "map":	true,
	    "not_found_ok":	true,
	]);
	if (modules == nil)
	{
	    return LDAPError ();
	}
	config_modules = mapmap (string dn, map mod, (map<string, map<string,any> >) modules, ``{
	    return $[dn : AddMissingAttributes (mod)];
	});
	return "";
    }

    /**
     * Search for one entry (=base scope) in LDAP directory
     * @param dn DN of entry
     * @return map with entry values, empty map if nothing found, nil on error
     */
    global define map GetLDAPEntry (string dn) ``{

	if (!ldap_initialized)
	{
	    string msg = LDAPInit ();
	    if (msg != "")
	    {
		LDAPErrorMessage ("init", msg);
		return nil;
	    }
	}
	if (!schema_initialized)
	{
	    string msg = InitSchema ();
	    if (msg != "")
	    {
		LDAPErrorMessage ("schema", msg);
		return nil;
	    }
	}
	if (bind_pass == nil && !anonymous)
	{
	    bind_pass = LDAPAskAndBind (true);
	    if (bind_pass == nil)
		return nil;
	}
	list objects = (list)SCR::Read (.ldap.search, $[
	    "base_dn":	dn,
	    "attrs":	[],
	    "scope":	0, // only this one
	    "not_found_ok": true
	]);
	if (objects == nil)
	{
	    LDAPErrorMessage ("read", LDAPError());
	    return nil;
	}
	return objects[0]:$[];
    }

    /**
     * Check for existence of parent object of given DN in LDAP tree
     * return the answer
     */
    global define boolean ParentExists (string dn) ``{

	if (! issubstring (dn, ","))
	    return false;

	string parent = substring (dn, search (dn,",")+1);
	map object = GetLDAPEntry (parent);
	if (object == nil)
	    return false;
	if (object == $[])
	{
	    if (!use_gui)
	    {
		y2error ("A direct parent for DN %1 does not exist in the LDAP directory. The object with the selected DN cannot be created.", dn);
		return false;
	    }
	    // error message, %1 is DN
	    Popup::Error (sformat (_("A direct parent for DN '%1' 
does not exist in the LDAP directory.
The object with the selected DN cannot be created.
"), dn));
	    return false;
	}
	return true;
    }

    /**
     * Return main configuration object DN
     */
    global define string GetMainConfigDN () ``{

	return base_config_dn;
    }

    /**
     * Return the map of configuration modules (new copy)
     * (in the form $[ DN: $[ map_of_one_module] ])
     */
    global define map GetConfigModules () ``{

	return (map) eval (config_modules);
    }

    /**
     * Return the map of templates (new copy)
     */
    global define map GetTemplates () ``{

	return (map) eval (templates);
    }

    /**
     * Return list of default object classes for user or group
     * There is fixed list here, it is not saved anywhere (only in default
     * users plugin for LDAP objects)
     * @param template used for differ if we need user or group list
     */
    global define list GetDefaultObjectClasses (map template) {

	list ocs = maplist (string c,template["objectClass"]:[],``(tolower(c)));

	if (contains (ocs, "susegrouptemplate"))
	{
	    return ["top", "posixGroup", "groupOfNames"];
	    // TODO sometimes there is groupofuniquenames...
	}
	else if (contains (ocs, "suseusertemplate"))
	{
	    return ["top","posixAccount","shadowAccount", "InetOrgPerson"];
	}
	return [];
    }

    /**
     * Creates default new map for a new object template
     * @param cn cn of new template
     * @param classes object classes of the object the template will belong to
     * @return template map
     */
    global define map CreateTemplate (string cn, list<string> classes) ``{

	map obj = $[
	    "cn":		[ cn ],
	    "modified":		"added"
	];
	classes = maplist (string c, classes, ``(tolower (c)));
	if (contains (classes, "suseuserconfiguration"))
	{
	    obj = union (obj, new_objects ["suseUserTemplate"]:$[]);
	}
	else if (contains (classes, "susegroupconfiguration"))
	{
	    obj = union (obj, new_objects ["suseGroupTemplate"]:$[]);
	}
	else
	{
	    obj["objectClass"] = [ "top", "suseObjectTemplate"];
	}

	obj = ConvertDefaultValues (obj);
	return AddMissingAttributes (obj);
    }

    /**
     * Creates default new map for new configuration object
     * @param class additional objectClass of new module (e.g.userConfiguration)
     * @return new module map
     */
    global define map<string,any> CreateModule (string cn, string class) ``{

	map obj = $[
	    "cn":		[ cn ],
	    "objectClass":	add (["top", "suseModuleConfiguration"], class),
	    "modified":		"added"
	];
	// create some good defaults
	obj = union (obj, new_objects [class]:$[]);
	list templs = [];
	string templ_cn = "";
	string default_base = "";
	if (tolower (class) == "suseuserconfiguration")
	{
	    foreach (string dn, map t, (map<string,map<string,any> >)templates,{
		list cls = maplist(string c,t["objectClass"]:[],``(tolower(c)));		if (contains (cls, "suseusertemplate"))
		    templs = add (templs, dn);
	    });
	    if (templs == [])
		templ_cn = "usertemplate";
	    default_base = sformat ("ou=people,%1", base_dn);

	    // for eDirectory, we have to use cleartext passwords!
	    if (nds && tolower (obj["susePasswordHash",0]:"") != "clear")
	    {
		obj["susePasswordHash"]	= [ "clear" ];
	    }
	}
	if (tolower (class) == "susegroupconfiguration")
	{
	    foreach (string dn, map t, (map<string,map<string,any> >)templates,{
		list cls = maplist(string c,t["objectClass"]:[],``(tolower(c)));
		if (contains (cls, "susegrouptemplate"))
		    templs = add (templs, dn);
	    });
	    if (templs == [])
		templ_cn = "grouptemplate";
	    default_base = sformat ("ou=group,%1", base_dn);
	}
	// create proposal for defaultTemplate DN
	if (templ_cn != "")
	{
	    string tdn = sformat ("cn=%1,%2", templ_cn, base_config_dn);
	    integer i = 0;
	    while (size (GetLDAPEntry (tdn)) > 0)
	    {
		tdn = sformat ("cn=%1%2,%3", templ_cn, i, base_config_dn);
		i = i + 1;
	    }
	    templs = [ tdn ];
	}
	obj ["suseDefaultTemplate"] = templs;
	obj ["suseDefaultBase"] = [ default_base ];
	return (map<string,any>) AddMissingAttributes (obj);
    }

    /**
     * Searches for DN's of all objects defined by filter in given base ("sub")
     * @param base search base
     * @param search_filter if filter is empty, "objectClass=*" is used
     * @return list of DN's (list of strings)
     */
    global define list<string> ReadDN (string base, string search_filter) ``{

	list<string> all = (list<string>)SCR::Read (.ldap.search, $[
	    "base_dn":	base,
	    "filter":   search_filter,
	    "attrs":	["cn"], // not necessary, just not read all values
	    "attrsOnly":true,
	    "scope":	2,
	    "dn_only":	true,
	]);
	if (all == nil)
	{
	    LDAPErrorMessage ("read", LDAPError());
	    return [];
	}
	return all;
    }

    /**
     * Returns DN's of groups (objectClass=posixGroup) in given base
     * @param base LDAP search base
     * @return groups (list of strings)
     */
    global define list GetGroupsDN (string base) ``{

	if (groups_dn == [])
	    groups_dn = ReadDN (base, "objectClass=posixGroup");
	return groups_dn;
    }

    /**
     * Check if given DN exist and if it points to some template
     * @param dn
     * @return empty map if DN don't exist, template map if DN points
     *  to template object, nil if object with given DN is not template
     */
    global define map CheckTemplateDN (string dn) ``{

	map object = GetLDAPEntry (dn);
	if (object == nil)
	    return nil;
	if (object == $[])
	{
	    // OK, does not exist
	    return $[];
	}
	list cls = maplist (string c, object["objectClass"]:[],``(tolower (c)));
	if (contains (cls, "suseobjecttemplate"))
	{
	    // exists as a template -> return object
	    object = ConvertDefaultValues (object);
	    object ["modified"] = "edited";
	    return AddMissingAttributes (object);
	}
	else
	{
	    // error message
	    Popup::Error (_("An object with the selected DN exists, but it is not a template object.
Select another one.
"));
	    return nil;
	}
    }

    /**
     * Save the edited map of configuration modules to global map
     */
    global define boolean CommitConfigModules (map modules) ``{

	foreach (string dn, map modmap, (map<string,map>) modules, {

	    if (!haskey (config_modules, dn))
	    {
		config_modules [dn] = eval (modmap);
		ldap_modified = true;
		return;
	    }
	    // 'val' can be list (most time), map (default_values), string
	    foreach (string attr, any val, (map<string,any>) modmap, ``{
		if (config_modules [dn, attr]:nil != val)
		{
		    config_modules [dn, attr] = val;
		    if (!haskey (modmap, "modified"))
			config_modules [dn, "modified"] = "edited";
		    ldap_modified = true;
		    y2debug ("modified value: %1", val);
		}
	    });
	});
	return true;
    }

    /**
     * Save the edited map of templates to global map
     */
    global define boolean CommitTemplates (map templs) ``{

	foreach (string dn, map template, (map<string,map>) templs, {

	    if (!haskey (templates, dn))
	    {
		// dn changed
		templates [dn] = eval (template);
		ldap_modified = true;
		return;
	    }
	    // 'val' can be list (most time), map (default_values), string
	    foreach (string attr, any val, (map<string,any>) template, ``{
		if (templates [dn, attr]:nil != val)
		{
		    templates [dn, attr] = val;
		    if (!haskey (template, "modified"))
			templates [dn, "modified"] = "edited";
		    ldap_modified = true;
		    y2debug ("modified value: %1", val);
		}
	    });
	});
	return true;
    }

    /**
     * Writes map of objects to LDAP
     * @param objects map of objects to write. It is in the form:
     * $[ DN: (map) attribute_values]
     * @example TODO
     * @return error map (empty on success)
     */
    global define map WriteToLDAP (map objects) ``{

	map ret = $[];
	foreach (string dn, map object, (map<string,map>) objects, {

	    if (ret != $[])
		return;
	    string action = object["modified"]:"";
	    if (action != "")
		object = remove (object, "modified");
	    else return;

	    // convert the default values back to the LDAP format
	    if (haskey (object, "default_values"))
	    {
		object["suseDefaultValue"] = maplist (string key, string val,
		    (map<string,string>) object["default_values"]:$[], ``{
		    return sformat ("%1=%2", key, val);
		});
		object = remove (object, "default_values");
	    }
	    if (action == "added")
	    {
		if (!(boolean)SCR::Write (.ldap.add, $[ "dn": dn ], object))
		    ret = LDAPErrorMap();
	    }
	    if (action == "edited")
	    {
		if (!(boolean)SCR::Write (.ldap.modify, $[
		    "dn":		dn,
		    "check_attrs":	true ], object))
		    ret = LDAPErrorMap();
	    }
	    if (action == "renamed")
	    {
		map arg_map	= $[
		    "dn"		: object["old_dn"]:dn,
		    "check_attrs"	: true
		];
		if (tolower (dn) != tolower (object["old_dn"]:dn))
		{
		    arg_map["new_dn"]		= dn;
		    arg_map["deleteOldRDN"]	= true;
		    arg_map["subtree"]		= true;
		}
		if (haskey (object, "old_dn"))
		    object  = remove (object, "old_dn");
		if (!(boolean)SCR::Write (.ldap.modify, arg_map, object))
		    ret = LDAPErrorMap();
	    }
	    if (action == "deleted")
	    {
		if (object ["old_dn"]:dn != dn)
		    dn = object ["old_dn"]:dn;
		if (!(boolean)SCR::Write (.ldap.delete, $[ "dn": dn ]))
		    ret = LDAPErrorMap();
	    }
	});
	return ret;
    }

    /**
     * Writes map of objects to LDAP. Ask for password, when needed and
     * shows the error message when necessary.
     * @return success
     */
    global define boolean WriteLDAP (map objects) ``{

	map error = $[];
	if (anonymous || bind_pass == nil)
	{
	    bind_pass = LDAPAskAndBind (false);
	}
	// nil means "canceled"
	if (bind_pass != nil)
	{
	    error = WriteToLDAP (objects);
	    if (error != $[])
	    {
		string msg	= error["msg"]:"";
		if (error["server_msg"]:"" != "")
		    msg		= msg + "\n" + error["server_msg"]:"";
		LDAPErrorMessage ("write", msg);
	    }
	}
	return (error == $[] && bind_pass != nil);
    }

    /**
     * Modify also /etc/openldap/ldap.conf for the use of
     * ldap client utilities (like ldapsearch)
     * @return modified?
     */
    global define boolean WriteOpenLdapConf () ``{

	boolean write_openldap_conf = openldap_modified;

	if (!Package::Installed ("openldap2-client"))
	    return false;

	map out = (map)SCR::Execute(.target.bash_output,
	    "/bin/rpm -V openldap2-client");

	list open_host	= (list) SCR::Read
	    (.etc.ldap_conf.v."/etc/openldap/ldap.conf".host);
	list open_base	= (list) SCR::Read
	    (.etc.ldap_conf.v."/etc/openldap/ldap.conf".base);

	// if the config file was not modified by user yet
	if (!issubstring(out["stdout"]:"", "/etc/openldap/ldap.conf"))
	    write_openldap_conf = true;
	// if there are same values as in /etc/ldap.conf
	else if(old_server == open_host[0]:"" && old_base_dn == open_base[0]:"")
	{
	    write_openldap_conf = true;
	}

	if (write_openldap_conf)
	{
	    // update ldap.conf
	    SCR::Write (.etc.ldap_conf.v."/etc/openldap/ldap.conf".host,
		[server]);
	    SCR::Write(.etc.ldap_conf.v."/etc/openldap/ldap.conf".base,
		[base_dn]);
	    y2milestone ("file /etc/openldap/ldap.conf was modified");
	}
	return write_openldap_conf;
    }


    /**
     * If a file does not + entry, add it.
     * @param	is login allowed?
     * @return	success?
     */
    global define boolean WritePlusLine (boolean login) ``{

	string file	= "/etc/passwd";
	string what	= "+::::::";
	if (!login)
	    what	= "+::::::/sbin/nologin";

	if (!passwd_read)
	{
	    if (!(boolean)SCR::Execute(.passwd.init,$["base_directory":"/etc"]))
	    {
		y2error ("error: %1", SCR::Read (.passwd.error));
		return false;
	    }
	    else
	    {
		passwd_read		= true;
		plus_lines_passwd	=
		    (list<string>) SCR::Read (.passwd.passwd.pluslines);
	    }
	}

	list<string> plus_lines	= plus_lines_passwd;

	if (!contains (plus_lines, what))
	{
	    plus_lines = maplist (string plus_line, plus_lines,``{
		if (!login && plus_line == "+::::::")
		    return what;
		if (login && issubstring (plus_line, ":/sbin/nologin"))
		    return what;
		return plus_line;
	    });
	    if (!contains (plus_lines, what))
		plus_lines	= add (plus_lines, what);

	    if ((boolean) SCR::Write (.passwd.passwd.pluslines, plus_lines))
	    {
		SCR::Execute (.target.bash,
		    sformat("/bin/cp %1 %1.YaST2save",file));
		// empty map as a parameter means "use data you have read"
		if (!SCR::Write (.passwd.users, $[]))
		{
		    Report::Error (Message::ErrorWritingFile (file));
		    return false;
		}
	    }
	}

	file	= "/etc/shadow";
	what	= "+";
	plus_lines	= (list<string>) SCR::Read (.passwd.shadow.pluslines);

	if (!contains (plus_lines, what) && !contains (plus_lines, "+::::::::"))
	{
	    plus_lines	= add (plus_lines, what);

	    if ((boolean) SCR::Write (.passwd.shadow.pluslines, plus_lines))
	    {
		SCR::Execute (.target.bash,
		    sformat("/bin/cp %1 %1.YaST2save",file));
		// empty map as a parameter means "use data you have read"
		if (!SCR::Write (.passwd.shadow, $[]))
		{
		    Report::Error (Message::ErrorWritingFile (file));
		    return false;
		}
	    }
	}
    }

    /**
     * Check if references to other objects are correct;
     * create these objects if possible
     */
    global define boolean CheckOrderOfCreation () {


	foreach (string dn, map m, (map<string,map>) config_modules, {

	    // 1. create suseDefaultBase object if not present

	    string base_dn	= m["suseDefaultBase",0]:"";
	    if (base_dn != "")
	    {
		map object = GetLDAPEntry (base_dn);
		if (object == nil)
		{
		    y2warning ("reference to nothing? (%1)", base_dn);
		    config_modules[dn]	= remove (m, "suseDefaultBase");
		}
		else if (object == $[])
		{
		    map default_base	= $[
			"objectClass"	: [ "top", "organizationalUnit"],
			"modified"	: "added",
			"ou"		: get_cn (base_dn)
		    ];
		    if (nds)
		    {
			default_base["acl"]	= [
			    "3#subtree#[Public]#[All Attributes Rights]",
			    "1#subtree#[Public]#[Entry Rights]"
			];
		    }
		    if (!ParentExists (base_dn) ||
			!WriteLDAP ($[ base_dn : default_base ]))
		    {
			y2error ("%1 cannot be created", base_dn);
			config_modules[dn]	= remove (m, "suseDefaultBase");
		    }
		}
	    }

	    // 2. empty template must be created when there is a reference
	    string template_dn	=  m["suseDefaultTemplate",0]:"";
	    if (template_dn != "" && ! haskey (templates, template_dn))
	    {
		map object = GetLDAPEntry (template_dn);
		if (size (object) == 0)
		{
		    y2milestone("template does not exist, creating default...");
		    string t_class =
			contains (m["objectClass"]:[],"suseGroupConfiguration")?
			"suseGroupTemplate" : "suseUserTemplate";
		    map template = $[
			"modified"	: "added",
			"cn"		: get_cn (template_dn)
		    ];
		    template = union (template, new_objects [t_class]:$[]);
		    if (!ParentExists (template_dn) ||
			!WriteLDAP ($[ template_dn: template ]))
		    {
			y2error ("%1 cannot be created", template_dn);
			config_modules[dn] = remove (m, "suseDefaultTemplate");
		    }
		}
	    }
	});

	// 3. check references to secondary groups in templates
	foreach (string dn, map m, (map<string,map>) templates, ``{

	    list groups		= m["suseSecondaryGroup"]:[];
	    if (size (groups) > 0)
	    {
		list new_groups	= [];
		foreach (string group, (list<string>) groups, ``{
		    map object = GetLDAPEntry (group);
		    if (object == nil || object == $[])
		    {
			y2warning ("no such group %1;removing reference",group);
		    }
		    else
		    {
			new_groups	= add (new_groups, group);
		    }
		});
		m["suseSecondaryGroup"]	= new_groups;
	    }
	});
	return true;
    }

    /**
     * create the default objects for users and groups
     */
    define boolean CreateDefaultLDAPConfiguration () ``{

	string msg	= "";
	if (!ldap_initialized)
	{
	    msg = LDAPInit ();
	    if (msg != "")
	    {
		LDAPErrorMessage ("init", msg);
		return false;
	    }
	}
	if (!schema_initialized)
	{
	    msg = InitSchema ();
	    if (msg != "")
	    {
	        LDAPErrorMessage ("schema", msg);
	    }
	}
	if (bind_pass != nil && !bound)
	{
	    msg = LDAPBind (bind_pass);
	    if (msg != "")
	    {
	        LDAPErrorMessage ("bind", msg);
	        bind_pass	= nil;
	    }
	}
	// create base configuration object
	map object = GetLDAPEntry (base_config_dn);
	if (object == nil)
	    return false;
	if (object == $[])
	{
	    if (ParentExists (base_config_dn))
	    {
		map config_object	= $[
		    "objectClass"	: [ "top", "organizationalUnit"],
		    "modified"		: "added",
		    "ou"		: get_cn (base_config_dn)
		];
		if (nds)
		{
		    config_object["acl"]	= [
			"3#subtree#[Public]#[All Attributes Rights]",
			"1#subtree#[Public]#[Entry Rights]"
		    ];
		}
	        if (!WriteLDAP ($[ base_config_dn : config_object ]))
	        {
		   y2error ("%1 cannot be created", base_config_dn);
	        }
	    }
	    //TODO fail?
	}

	map modules		= $[];
	map templs		= $[];
	string user_dn	= get_dn ("userconfiguration");
	string group_dn	= get_dn ("groupconfiguration");

	if (config_modules == $[])
	{
	    ReadConfigModules ();
	}

	// check which objects already exist...
	foreach (string dn, map m, (map<string,map>) config_modules, ``{
	    list cl = maplist (string c, m["objectClass"]:[],``(tolower(c)));
	    if (contains (cl, "suseuserconfiguration"))
	        user_dn = dn;
	    if (contains (cl, "susegroupconfiguration"))
	        group_dn = dn;
	});

	// create user configuration object
	if (config_modules[user_dn]:$[] == $[] &&
	    GetLDAPEntry (user_dn) == $[])
	{
	    modules [user_dn] = CreateModule (
	        get_cn (user_dn), "suseUserConfiguration");
	}

	// create group configuration object
	if (config_modules[group_dn]:$[] == $[] &&
	    GetLDAPEntry (group_dn) == $[])
	{
	    modules [group_dn] = CreateModule (
	        get_cn (group_dn), "suseGroupConfiguration");
	}

	CommitConfigModules (modules);
	modules = GetConfigModules ();
	boolean update_modules	= false;

	// create user template...
	string template_dn	=
	    get_string (modules[user_dn]:$[], "suseDefaultTemplate");
	if (modules [user_dn,"suseDefaultTemplate"]:[] == [])
	{
	    template_dn	= "cn=usertemplate," + base_config_dn;
	    modules [user_dn,"suseDefaultTemplate"]	= [ template_dn ];
	    update_modules		= true;
	}

	if (templates[template_dn]:$[] == $[] &&
	    GetLDAPEntry (template_dn) == $[])
	{
	    templs [template_dn]	= CreateTemplate (
	        get_cn (template_dn), ["suseUserConfiguration"]);
	}

	// group template...
	template_dn	=
	    get_string (modules[group_dn]:$[], "suseDefaultTemplate");
	if (modules [group_dn,"suseDefaultTemplate"]:[] == [])
	{
	    template_dn	= "cn=grouptemplate," + base_config_dn;
	    modules [group_dn,"suseDefaultTemplate"] = [ template_dn ];
	    update_modules		= true;
	}

	if (templates[template_dn]:$[] == $[] &&
	    GetLDAPEntry (template_dn) == $[])
	{
	    templs [template_dn]	= CreateTemplate (
	        get_cn (template_dn), ["suseGroupConfiguration"]);
	}

	if (update_modules)
	    CommitConfigModules (modules);
	CommitTemplates (templs);
	return true;
    }

    /**
     * Check the server if it is NDS (novell directory service)
     */
    global define boolean CheckNDS () {

	if (!ldap_initialized)
	{
	    string msg = LDAPInit ();
	    if (msg != "")
	    {
		// no popup: see bug #132909
		return false;
	    }
	}

	list vendor = (list) SCR::Read (.ldap.search, $[
	    "base_dn"       : "",
	    "scope"         : 0,
	    "attrs"         : [ "vendorVersion", "vendorName" ]
	]);

	y2debug ("vendor: %1", vendor);
	map<string,list> output	= vendor[0]:$[];
	foreach (string attr, list value, output, {
	    if (issubstring (value[0]:"", "Novell"))
	    {
		y2debug ("value: %1", value[0]:"");
		nds	= true;
	    }
	});

	nds_checked	= true;
	return nds;
    }

    /**
     * Saves LDAP configuration.
     * @param abort block for abort
     * @return symbol
     */
    global define symbol Write (block<boolean> abort) {

	// progress caption
	string caption = _("Writing LDAP Configuration...");
	integer no_of_steps = 4;

	Progress::New (caption, " ", no_of_steps, [
	    // progress stage label
	    _("Stop services"),
	    // progress stage label
	    _("Update configuration files"),
	    // progress stage label
	    _("Start services"),
	    // progress stage label
	    _("Update configuration in LDAP directory"),
           ], [
	    // progress step label
	    _("Stopping services..."),
	    // progress step label
	    _("Updating configuration files..."),
	    // progress step label
	    _("Starting services..."),
	    // progress step label
	    _("Updating configuration in LDAP directory..."),
	    // final progress step label
	    _("Finished") ],
	    "" );

	// -------------------- stop services
	Progress::NextStage ();
	if (eval (abort))
	    return `abort;

	boolean ypbind_running = false;

	if (!write_only)
	{
	    ypbind_running = (Service::Status ("ypbind") == 0);
	    Service::Stop ("ypbind");
	}
	else if (write_only && Mode::autoinst () )
	{
	    // Read existing nsswitch in autoinstallation mode
	    foreach (string db,
		["passwd","group","passwd_compat","group_compat"],``{
		nsswitch[db]	= Nsswitch::ReadDb (db);
	    });
	}

	// -------------------- update config files
	Progress::NextStage ();
	if (eval(abort))
	    return `abort;

	if (modified)
	{
	    // update ldap.conf
	    WriteLdapConfEntry ("host", server);
	    WriteLdapConfEntry ("base", base_dn);

	    if (member_attribute != old_member_attribute)
	    {
		WriteLdapConfEntries ("nss_map_attribute",
		    ["uniqueMember", member_attribute ]);
	    }

	    WriteOpenLdapConf();

	    if (ldap_v2)
		WriteLdapConfEntry ("ldap_version", "2");
	    else
		WriteLdapConfEntry ("ldap_version", "3");

	    if (ldap_tls)
		WriteLdapConfEntry ("ssl", "start_tls");
	    else
		WriteLdapConfEntry ("ssl", "no");

	    WriteLdapConfEntry ("tls_cacertdir", tls_cacertdir == "" ? nil : tls_cacertdir);
	    WriteLdapConfEntry ("tls_cacertfile", tls_cacertfile == "" ? nil : tls_cacertfile);

	    Pam::Set ("mkhomedir", mkhomedir);

	    WriteLdapConfEntry ("pam_password", pam_password);

	    // see bugs #suse37665 (pam_filter necessary), #118779 (not always)
	    if (ReadLdapConfEntry ("pam_filter", "") == "")
	    {
		AddLdapConfEntry ("pam_filter", "objectClass=posixAccount");
	    }

	    // save the user and group bases
	    user_base = base_dn;
	    group_base = base_dn;

	    WriteLdapConfEntry ("nss_base_passwd",
		(nss_base_passwd != base_dn && nss_base_passwd != "") ?
		nss_base_passwd : nil);
	    WriteLdapConfEntry ("nss_base_shadow",
		(nss_base_shadow != base_dn && nss_base_shadow != "") ?
		nss_base_shadow : nil);
	    WriteLdapConfEntry ("nss_base_group",
		(nss_base_group != base_dn && nss_base_group != "") ?
		nss_base_group : nil);

	    // default value is 'yes'
	    WriteLdapConfEntry ("tls_checkpeer", tls_checkpeer == "yes" ? nil : tls_checkpeer);
	}
	if (start) // ldap used for authentocation
	{
	    // ---------- correct pam_password value for Novell eDirectory
	    if (pam_password != "nds" && expert_ui)
	    {
		if (!nds_checked && !Mode::autoinst ())
		{
		    CheckNDS ();
		}
		if (nds)
		{
		    pam_password	= "nds";
		}
		WriteLdapConfEntry ("pam_password", pam_password);
	    }


	    if (!oes)
	    {
		// pam settigs
		if (Pam::Enabled("krb5"))
		{
		    // If kerberos is used for authentication we configure
		    // pam_ldap in a way that we use only the account checking.
		    // Other configuration would mess up password changing
		    Pam::Add ("ldap-account_only");
		}
		else
		{
		    Pam::Add ("ldap");
		}
		// modify sources in /etc/nsswitch.conf
		Nsswitch::WriteDb ("passwd", ["compat"]);
		Nsswitch::WriteDb ("passwd_compat", (list<string>)
		    union (nsswitch["passwd_compat"]:[], ["ldap"]));

		foreach (string db, ["services","netgroup","aliases"], {
		    Nsswitch::WriteDb (db, ["files", "ldap"]);
		});

		if (contains (nsswitch["group"]:[], "compat") &&
		    contains (nsswitch["group_compat"]:[], "ldap"))
		{
		    y2milestone ("group_compat present, not changing");
		}
		else
		{
		    Nsswitch::WriteDb ("group", ["files", "ldap"]);
		}

		Nsswitch::Write ();
	    }
	    Autologin::Write (write_only);
	}
	else if (!oes) // ldap is not used
	{
	    //TODO: first check, if nss needs to be updated...
	    foreach (string db, [ "passwd", "group" ], ``{
		string new_db		= db+"_compat";
		nsswitch [db]	= filter (
		    string v, nsswitch[db]:[], ``(v != "ldap"));
		if (nsswitch[db]:[] == [] || nsswitch[db]:[] == ["files"])
		    nsswitch [db]	= ["compat"];
		nsswitch [new_db]	= filter (
		    string v, nsswitch[new_db]:[], ``(v != "ldap"));
		Nsswitch::WriteDb (db, nsswitch[db]:["compat"]);
		Nsswitch::WriteDb (new_db, nsswitch[new_db]:[]);
	    });

	    foreach (string db, ["services" ,"netgroup", "aliases" ], {
		list<string> db_l	= (list<string>) filter (
		    string v, Nsswitch::ReadDb (db), ``(v != "ldap"));
		if (db_l == [])
		    db_l	= ["files"];
		Nsswitch::WriteDb (db, db_l);
	    });

	    Nsswitch::Write ();

	    if (Pam::Enabled("ldap"))
	    {
		Pam::Remove ("ldap");
	    }
	    else if(Pam::Enabled("ldap-account_only"))
	    {
		Pam::Remove ("ldap-account_only");
	    }
	}


	// write the changes in /etc/ldap.conf and /etc/openldap/ldap.conf now
	if (!SCR::Write(.etc.ldap_conf, nil))
	{
	    y2error ("error writing ldap.conf file");
	}
	if (Stage::cont ())
	{
	    SCR::UnmountAgent (.etc.ldap_conf);
	}

	// write sysconfig values
	SCR::Write (.sysconfig.ldap.FILE_SERVER, file_server? "yes": "no");

	SCR::Write (.sysconfig.ldap.BASE_CONFIG_DN, base_config_dn);

	SCR::Write (.sysconfig.ldap.BIND_DN, bind_dn);

	// write the changes in /etc/sysconfig/ldap now
	if (!SCR::Write (.sysconfig.ldap, nil))
	{
	    y2error ("error writing /etc/sysconfig/ldap");
	}

	if (_autofs_allowed)
	{
	    if (Nsswitch::WriteAutofs (start && _start_autofs, "ldap"))
	    {
		if (_start_autofs)
		{
		    Service::Adjust ("autofs", "enable");
		}
		else
		{
		    Service::Adjust ("autofs", "disable");
		}
	    }
	}

	if (start)
	    WritePlusLine (login_enabled);

	// -------------------- start services
	Progress::NextStage ();
	if (eval(abort))
	    return `abort;

	if (!write_only)
	{
	    if (Package::Installed ("nscd") && modified)
	    {
		SCR::Execute (.target.bash, "/usr/sbin/nscd -i passwd");
		SCR::Execute (.target.bash, "/usr/sbin/nscd -i group");
		Service::RunInitScript ("nscd", "try-restart");
	    }

	    if (Package::Installed ("zmd") && Service::Status("novell-zmd")== 0)
	    {
		Service::RunInitScript ("novell-zmd", "try-restart");
	    }

	    if (ypbind_running)
	    {
		Service::Restart ("ypbind");
	    }

	    if (restart_sshd)
	    {
		Service::Restart ("sshd");
	    }

	    if (_autofs_allowed)
	    {
		Service::Stop ("autofs");

		if (_start_autofs)
		{
		    Service::Start ("autofs");
		}
	    }
	    // after finish of 2nd stage, restart running services (bnc#395402)
	    if (start && Stage::cont ())
	    {
		list<string> services	= [];
		foreach (string service, ["dbus", "haldaemon" ], {
		    if (Service::Status (service) == 0)
			services	= add (services, service);
		});
		if (size (services) > 0)
		{
		    y2milestone ("services %1 will be restarted", services);
		    SCR::Write (.target.string,
			Directory::vardir + "/restart_services",
			mergestring (services, "\n") + "\n"
		    );
		}
	    }
	}

	// -------------------- write settings to LDAP
	Progress::NextStage ();
	if (eval(abort))
	    return `abort;

	// ------------------------------ create the LDAP configuration (#40484)
	boolean ldap_ok = true;
	if (create_ldap && !Mode::autoinst ())
	{
	    ldap_ok	= CreateDefaultLDAPConfiguration ();
	}

	if (ldap_modified && ldap_ok)
	{
	    CheckOrderOfCreation ();

	    if (WriteLDAP (templates) && WriteLDAP (config_modules)) {
		ldap_modified	= false;
	    }
	}
	if (ppolicies != $[])
	{
	    WriteLDAP (ppolicies);
	    modified	= true; // so data get reset in next step
	    ppolicies	= $[];
	}

	// final stage
	Progress::NextStage ();

	// unbind is done in agent destructor
	// ldap-client can be called more times from users module so we
	// will have to know it is necessary to bind again
	bound = false;
	if (modified)
	{
	    ldap_initialized    = false;
	    old_server          = server;
	    old_base_dn         = base_dn;
	}
	if (ldap_modified)
	{
	    config_modules  = $[];
            templates       = $[];
	}

	// now clear the initial default values, so next time Read will read
	// real values
	if (Stage::cont () && size (initial_defaults) > 0)
	{
	    string first_s	= GetFirstServer (server);
	    if (start && ldap_ok &&
		base_dn == initial_defaults["ldap_domain"]:"" &&
		(first_s == initial_defaults["ldap_server"]:"" ||
		 DNS::IsHostLocal (first_s)))
	    {
		initial_defaults_used	= true;
		y2milestone ("initial defaults were used");
	    }
	    initial_defaults	= $[];
	}

	return `next;
    }

    /**
     * wrapper for Write, without abort block
     */
    global define boolean WriteNow () {

	block<boolean> abort = ``{ return false; };

	list<string> needed_packages = ["pam_ldap", "nss_ldap"];

	if (_start_autofs && !Package::Installed("autofs"))
	{
	   needed_packages	= add (needed_packages, "autofs");
	}

	if (start && !Package::InstalledAll (needed_packages))
	{
	    if (!Package::InstallAll (needed_packages))
		Report::Error (Message::FailedToInstallPackages ());
	    start		= false;
	    _start_autofs	= false;
	}
	// during CLI call nss_base_* are not edited: adapt them to new base DN
	if (old_base_dn != base_dn && nss_base_passwd == old_base_dn)
	{
	    nss_base_passwd	= base_dn;
	    nss_base_shadow	= base_dn;
	    nss_base_group	= base_dn;
	}

	return (Write(abort) == `next);
    }


    /**
     * Check if base config DN belongs to some existing object and offer
     * creating it if necessary
     */
    global define boolean CheckBaseConfig (string dn) ``{

	map object = GetLDAPEntry (dn);
	if (object == nil)
	{
	    return false;
	}
	if (object == $[])
	{
	    // yes/no popup, %1 is value of DN
	    if (!use_gui || Popup::YesNo (sformat (_("No entry with DN '%1'
exists on the LDAP server. Create it now?
"), dn)))
	    {
		if (!ParentExists (dn))
		    return false;
		map config_object	= $[
		    "objectClass"	: [ "top", "organizationalUnit"],
		    "modified"		: "added",
		    "ou"		: get_cn (dn)
		];
		if (nds)
		{
		    config_object["acl"]	= [
			"3#subtree#[Public]#[All Attributes Rights]",
			"1#subtree#[Public]#[Entry Rights]"
		    ];
		}
		return WriteLDAP ( $[ dn : config_object ]);
	    }
	    return false;
	}
	return true;
    }

    /**
     * Set the value of bind_pass variable
     * @param pass new password valure
     */
    global define void SetBindPassword (string pass) ``{
	bind_pass = pass;
    }

    /**
     * Set the value of 'anonymous' variable (= bind without password)
     * @param anon new value
     */
    global define void SetAnonymous (boolean anon) ``{
	anonymous = anon;
    }

    /**
     * Set the value of 'use_gui' variable (= show error popups)
     * @param gui new value
     */
    global define void SetGUI (boolean gui) ``{
	use_gui	= gui;
    }

    /**
     * Set the value of restart_sshd (= restart sshd during write)
     */
    global define void RestartSSHD (boolean restart) {
	restart_sshd	= restart;
    }
}

ACC SHELL 2018