ACC SHELL

Path : /var/lib/ntp/proc/self/root/usr/share/YaST2/include/ldap/
File Upload :
Current File : //var/lib/ntp/proc/self/root/usr/share/YaST2/include/ldap/ui.ycp

/**
 * File:	include/ldap/ui.ycp
 * Package:	Configuration of LDAP
 * Summary:	User interface functions.
 * Authors:	Thorsten Kukuk <kukuk@suse.de>
 *		Anas Nashif <nashif@suse.de>
 *
 * $Id: ui.ycp 61773 2010-04-20 09:54:37Z jsuchome $
 *
 * All user interface functions.
 */

{
    textdomain "ldap-client";

    import "Address";
    import "Autologin";
    import "Directory";
    import "FileUtils";
    import "Label";
    import "Ldap";
    import "LdapPopup";
    import "Message";
    import "Mode";
    import "Package";
    import "Pam";
    import "Popup";
    import "Report";
    import "Service";
    import "SLPAPI";
    import "Stage";
    import "Wizard";

    include "ldap/routines.ycp";

    define boolean Modified () ``{

	return (Ldap::modified || Ldap::ldap_modified || Ldap::ppolicies != $[]);
    }

    /**
     * The dialog that appears when the [Abort] button is pressed.
     * @return `abort if user really wants to abort, `back otherwise
     */
    define symbol ReallyAbort () ``{

	boolean ret = true;
	if (!Stage::cont () || contains (WFM::Args (), "from_users"))
	{
	    ret = Modified() ? Popup::ReallyAbort (true) : true;
	}
	else
	{
	    ret = Popup::ConfirmAbort (`incomplete);
	}

	if ( ret )	return `abort;
	else		return `back;
    }

    /**
     * Read settings dialog
     * @return `abort if aborted and `next otherwise
     */
    define symbol ReadDialog () ``{
	boolean ret = Ldap::Read();
        return ret ? `next : `abort;
    }

    /**
     * Write settings dialog
     * @return `next
     */
    define symbol WriteDialog() ``{

	// popup text
	block<boolean> abort = ``{
            if (UI::PollInput () == `abort &&
		// popup text
		Popup::YesNo (_("Really abort the writing process?")))
		return true;
	    return false;
	};

	if (Modified())
	{
	    // help text
	    Wizard::RestoreHelp(_("Writing LDAP Client Settings"));
	    return Ldap::Write (abort);
	}
	return `next;
    }

    /**
     * Check syntax of entry with servers:
     * multiple adresses are allowed (separated by spaces), address may contain
     * port number
     */
    define boolean check_address (string servers) {

	boolean ret	= true;
	foreach (string server, splitstring (servers, " \t"), {
	    if (server == "") return;
	    if (issubstring (server, ":"))
	    {
		integer pos	= search (server, ":");
		string port	= substring (server, pos + 1);
		if (port == "" || tointeger (port) == nil)
		    ret = false;
		else
		    ret = ret && Address::Check (substring (server, 0, pos));
	    }
	    else
		ret = ret && Address::Check (server);
	});
	return ret;
    }

    /**
     * Select from LDAP servers provided by SLP
     */
    define string BrowseServers () {

	string servers	= "";

	UI::OpenDialog (
	    // popup window
	    `Label (_("Scanning for LDAP servers provided by SLP...")));

	UI::BusyCursor ();

	list items	= [];
	foreach (map service, SLPAPI::FindSrvs ("service:ldap", ""), {
	    string s	= service["pcHost"]:"";
	    /* TODO take address+port from url
	    if (service["pcHost"]:"" == "")
		s	= service["srvurl"]:"";
	    */
	    if (s != "")
		items	= add (items, s);
	});
	UI::CloseDialog ();
	UI::OpenDialog (`VBox(
	    `HSpacing (36),
	    `ReplacePoint (`id (`rp),
		`MultiSelectionBox (`id(`sel),
		// multiselection box label
		_("LDAP &Servers Provided by SLP"), items)
	    ),
	    `HBox (
		`PushButton (`id(`ok), Label::OKButton()),
		`PushButton (`id(`cancel), Label::CancelButton())
	    )
	));
	UI::NormalCursor ();
	any ret = "";
	do
	{
	    ret		= UI::UserInput ();
	    servers	= mergestring (
		(list<string>)UI::QueryWidget (`id (`sel),`SelectedItems), " ");

	} while (ret != `ok && ret != `cancel);

	UI::CloseDialog ();
	return ret == `ok ? servers : "";
    }

    /**
     * The main dialog for ldap-client configuration
     * @return	`back, `next or `abort
     */
    define symbol LdapDialog () {

	// help text 1/9
	string help_text = _("<p>Here, your machine can be set up as an
LDAP client.</p>
") +

	// help text 2/9
	_("<p>To authenticate your users with an OpenLDAP server, select <b>Use LDAP</b>. NSS and PAM will be configured accordingly.</p>") +

	// help text 3/9
	_("<p>To deactivate LDAP services, click <b>Do Not Use LDAP</b>.
If you deactivate LDAP, the current LDAP entry for passwd in /etc/nsswitch.conf
will be removed. The PAM configuration will be modified and the LDAP entry
removed.</p>") +

	// help text 3.5/9
	_("<p>To activate LDAP but forbid users from logging in to this machine, select <b>Enable LDAP Users but Disable Logins</b>.</p>") +

	// help text 4/9
	_("<p>Enter the LDAP server's address (such as ldap.example.com or 10.20.0.2) in <b>Addresses</b> and the distinguished name of the search base (<b>Base DN</b>, such as dc=example,dc=com). Specify multiple servers
by separating their addresses with spaces. It must be possible to resolve the
addresses without using LDAP. You can also specify the port on which the server is running using the syntax \"server:port\", for example, <tt>ldap.example.com:379</tt>.
</p>
") +

	// help text 5/9
	_("<p>With <b>Find</b>, select the LDAP server from the list provided by the service location protocol (SLP). Using <b>Fetch DN</b>, read the base DN from server.</p>") +

	// help text 6/9
	_("<p>Some LDAP servers support StartTLS (RFC2830).
If your server supports it and it is configured, activate <b>LDAP TLS/SSL</b>
to encrypt your communication with the LDAP server. You may download CA certificate file in PEM format from given URL.</p>
") +

	// help text 8/9
	_("<p>For configuration of advanced LDAP settings, click
<b>Advanced Configuration</b>.</p>
");
	// help text 9/9 (additional)
	string autofs_help_text = _("<p><b>Automounter</b> is a daemon that automatically mounts directories,
such as users' home directories.
It is assumed that its configuration files (auto.*) already exist
locally or over LDAP.
If it is not installed and you want to use it, it is installed
automatically.</p>
");


	// during installation, starting ldap is default
	boolean installation	=
	    Stage::cont () && !contains (WFM::Args (), "from_users");
	boolean start		= Ldap::start || installation;

	string base_dn		= Ldap::GetBaseDN ();
	string server		= Ldap::server;
	boolean ldap_tls	= Ldap::ldap_tls;
	string tls_checkpeer	= Ldap::tls_checkpeer;
	boolean login_enabled	= Ldap::login_enabled;
	string certTmpFile	= sformat ("%1/__LDAPcert.crt", Directory::tmpdir);

	boolean autofs		= Ldap::_start_autofs;
	term autofs_con		= `Empty ();
	if (Ldap::_autofs_allowed)
	{
	    autofs_con		= `VBox (
		`VSpacing (0.5),
		// check box label
		`Left(`CheckBox (`id(`autofs), _("Start Auto&mounter"), autofs))
	    );
	    help_text	= help_text + autofs_help_text;
	}

	boolean mkhomedir	= Ldap::mkhomedir;
	term mkhomedir_term	= `VBox (
	    Ldap::_autofs_allowed ? `VSpacing (0) : `VSpacing (0.5),
	    `Left(`CheckBox(`id(`mkhomedir),
		// checkbox label
		_("Create Home Directory on Login"), mkhomedir
	    ))
	);

	term con = `VCenter (`HBox (`HSpacing (3), `VBox (
	    `VSpacing (0.5),
	    // frame label
	    `Frame (_("User Authentication"), `HBox (`HSpacing (0.5), `VBox (
		`VSpacing (0.4),
		`RadioButtonGroup (`id(`rd),
		    `Left(`HVSquash(`VBox (
			`Left (`RadioButton(`id(`ldapno), `opt (`notify),
			    // radio button label
			    _("Do N&ot Use LDAP"), !start)),
			`Left(`RadioButton(`id(`ldapyes), `opt (`notify),
			    // radio button label
			    _("&Use LDAP"), start)),
			`Left(`RadioButton(`id(`ldapnologin), `opt (`notify),
			    // radio button label
			    _("Use LDAP but Disable &Logins"),
				start && !login_enabled))
		    )))
		),
		`VSpacing (0.4)
	    ))),
	    `VSpacing (0.4),
	    // frame label
	    `Frame (_("LDAP Client"), `HBox (`HSpacing (0.5), `VBox(
		`VSpacing (0.4),
		`HBox (
		    // text entry label
		    `InputField (`id (`server), `opt (`hstretch),
			_("Addresses of LDAP &Servers"), server),
		    `VBox (
			`Label (""),
			// push button label
			`PushButton (`id(`slp), _("F&ind"))
		    )
		),
		`HBox (
		    `InputField (`id (`ldapbasedn),  `opt (`hstretch),
			// text entry label
			_("LDAP Base &DN"), base_dn),
		    `VBox (
			`Label (""),
			// push button label
			`PushButton (`id(`fetch), _("F&etch DN"))
		    )
		),
		`VSpacing (0.4)
	    ), `HSpacing (0.5))),
	    `Frame (_("Secure Connection"), `HBox (`HSpacing (0.5), `VBox(
		`HBox (
		    // check box label
		    `Left (`CheckBox (`id(`ldaps), `opt (`notify), _("LDAP &TLS/SSL"), ldap_tls)),
		    // push button label
		    `PushButton (`id(`import_cert), _("Download CA Certificate"))
		),
		`VSpacing (0.2)
	    ), `HSpacing (0.5))),
	    autofs_con,
	    mkhomedir_term,
	    `VSpacing(0.4),
	    // pushbutton label
	    `PushButton (`id(`advanced), _("&Advanced Configuration..."))
        ), `HSpacing (3)));

	Wizard::SetContentsButtons (
	    // dialog title
	    _("LDAP Client Configuration"), con, help_text,
	    Stage::cont () ? Label::BackButton() : Label::CancelButton (),
	    Stage::cont () ? Label::NextButton() : Label::OKButton());

	if (Stage::cont ())
	    Wizard::RestoreAbortButton ();
	else
	    Wizard::HideAbortButton ();


	UI::ChangeWidget (`id(`server),`ValidChars, Address::ValidChars + " ");
	UI::ChangeWidget (`id(`import_cert),`Enabled, ldap_tls);

	symbol result = `not_next;
	do {
	    result = (symbol) UI::UserInput ();

	    any rb	= UI::QueryWidget(`id(`rd), `CurrentButton);
	    start	= (rb != `ldapno);
	    login_enabled = (rb != `ldapnologin);

	    server	= (string) UI::QueryWidget(`id(`server), `Value);
	    ldap_tls	= (boolean) UI::QueryWidget(`id(`ldaps), `Value);
	    mkhomedir	= (boolean) UI::QueryWidget (`id(`mkhomedir),`Value);

	    UI::ChangeWidget (`id(`import_cert), `Enabled, ldap_tls);

	    if (result == `slp)
	    {
		string srv	= "";
		if (!Package::Installed ("yast-slp"))
		{
		    if (Package::Install ("yast2-slp"))
		    {
			SCR::RegisterAgent (.slp, `ag_slp(`SlpAgent()));
			srv = BrowseServers ();
		    }
		}
		else srv = BrowseServers ();
		if (srv != "")
		    UI::ChangeWidget (`id(`server), `Value, srv);
	    }
	    if (result == `fetch)
	    {
		Ldap::tls_switched_off	= false;
		string dn = Ldap::ldap_initialized ?
		    LdapPopup::BrowseTree ("") :
		    LdapPopup::InitAndBrowseTree ("", $[
			"hostname"	: Ldap::GetFirstServer (server),
			"port"		: Ldap::GetFirstPort (server),
			"version"	: Ldap::ldap_v2 ? 2 : 3,
			"use_tls"	: ldap_tls ? "yes" : "no"
		    ]);
		if (dn != "")
		    UI::ChangeWidget (`id(`ldapbasedn), `Value, dn);
		// adapt the checkbox value
		if (Ldap::tls_switched_off)
		{
		    UI::ChangeWidget (`id (`ldaps), `Value, false);
		}
	    }
	    if (result == `import_cert)
	    {
		string dir		= Ldap::tls_cacertdir;
		if (Ldap::tls_cacertdir == "")
		    dir = "/etc/openldap/cacerts/";

		UI::OpenDialog ( `opt(`decorated), `HBox(
		    `HSpacing(1),
		    `VBox (
			`HSpacing (75),
			// InputField label
			`InputField (`id (`url),  `opt (`hstretch),
			    _("CA Certificate URL for Download")),
			`HBox (
			    `PushButton(`id(`ok),`opt(`default,`key_F10), Label::OKButton()),
			    `PushButton(`id(`cancel),`opt (`key_F9), Label::CancelButton())
			)
		    ),
		    `HSpacing(1)
		));
		UI::SetFocus (`id (`url));

		any ret	= nil;
		boolean success	= false;
		string name	= "";

		while (true)
		{
		    ret	= UI::UserInput ();
		    if (ret == `cancel)
			break;
		    if (ret == `ok)
		    {
			string cert_url = (string) UI::QueryWidget (`id (`url), `Value);
			string curlcmd = sformat("curl -f --connect-timeout 60  --max-time 120  '%1' -o  %2", cert_url, certTmpFile);

			if (SCR::Execute(.target.bash, curlcmd) != 0)
			{
			    // error message
			    Popup::Error (_("Could not download the certificate file from specified URL."));
			}
			else if (FileUtils::CheckAndCreatePath (dir))
			{
			    list <string> l = splitstring (cert_url, "/");
			    name	= l[size(l) - 1]:"downloaded-by-yast2-ldap-client.pem";
			    success = SCR::Execute (.target.bash, sformat ("/bin/cp -a '%1' '%2/%3'", certTmpFile, dir, name)) == 0;
			    break;
			}
		    }
		}
		UI::CloseDialog ();

		if (ret == `cancel)
		{
		    continue;
		}
		if (success)
		{
		    // popup message, %1 is file name, %2 directory
		    Popup::Message (sformat (_("The downloaded certificate file

'%1'

was copied to '%2' directory"), name, dir));

		    Ldap::tls_cacertdir	= dir;
		    Ldap::modified	= true;
		}
	    }

	    if (result == `next || result == `advanced)
	    {
		base_dn	= (string) UI::QueryWidget(`id(`ldapbasedn), `Value);
		autofs	= Ldap::_autofs_allowed &&
		    (boolean) UI::QueryWidget (`id (`autofs), `Value);
	    }

	    if ((result == `next || result == `advanced) && start)
	    {
		if (base_dn == "")
		{
		    // error popup label
                    Report::Error(_("Enter an LDAP base DN."));
                    result = `not_next;
                    continue;
                }

		if (server == "" || deletechars (server, " \t") == "")
		{
		    // error popup label
		    Report::Error(_("Enter at least one address of an LDAP server."));
		    result = `not_next;
		    continue;
		}
		if (!check_address (server))
		{
		    // error popup label
		    Report::Error (_("The LDAP server address is invalid.") + "\n\n" + Address::Valid4 ());
		    UI::SetFocus (`id(`server));
		    result	= `not_next;
		    continue;
		}
	    }
	    if ((result == `abort || result == `cancel || result == `back) &&
		ReallyAbort () != `abort)
	    {
		result = `not_next;
	    }
	    if (result == `next && (start || autofs))
	    {
		if (start && !Ldap::start && Ldap::nis_available)
		{
		    // popup question: user enabled LDAP now, but probably has
		    // enabled NIS client before
		    if (!Popup::YesNo (_("When you configure your machine as an LDAP client,
you cannot retrieve data with NIS. Really use LDAP instead of NIS?
")))
		    {
			result = `not_next;
			continue;
		    }
		}

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

		if (start && !Package::InstalledAll (needed_packages))
		{
		    Ldap::required_packages = (list<string>) union (
			Ldap::required_packages, needed_packages);
		}

		if (autofs && !Package::Installed("autofs"))
		{
		    Ldap::required_packages = (list<string>) union (
			Ldap::required_packages, ["autofs"]);
		}
		if (!Package::InstallAll(Ldap::required_packages))
		{
		    if (start && !Package::InstalledAll (needed_packages))
		    {
			Popup::Error (Message::FailedToInstallPackages ());
			start = false;
			result = `not_next;
			if (!installation)
			    UI::ChangeWidget(`id(`rd), `CurrentButton, `ldapno);
			continue;
		    }
		}
	    }
	} while (!contains ([`back, `next, `cancel, `abort, `advanced], result));

	if (result == `next || result == `advanced)
	{
	    if (Ldap::GetBaseDN() != base_dn &&
		Ldap::nss_base_passwd == Ldap::GetBaseDN ())
	    {
		Ldap::nss_base_passwd	= base_dn;
		Ldap::nss_base_shadow	= base_dn;
		Ldap::nss_base_group	= base_dn;
	    }
	    if (Ldap::start != start || Ldap::GetBaseDN() != base_dn ||
		Ldap::server != server ||
		Ldap::ldap_tls != ldap_tls || Ldap::_start_autofs != autofs ||
		Ldap::login_enabled != login_enabled ||
		Ldap::mkhomedir != mkhomedir)
	    {
		if (result == `next)
		{
		    if (start && !Ldap::start && !Mode::config())
		    {
			Autologin::AskForDisabling (
			    // popup text
			    _("LDAP is now enabled."));

			string message	= Stage::cont () ? "" :
			// message popup, part 1/2
_("This change only affects newly created processes and not already
running services. Restart your services manually or reboot 
the machine to enable it for all services.
");

			if (Service::Status ("sshd") == 0)
			{
			    Ldap::RestartSSHD (true);
			    // message popup, part 1/2
			    message = message + _("
To enable remote login for LDAP users, sshd is
restarted automatically by YaST.
");
			}
			if (message != "")
			    Popup::Message (message);
		    }
		    if (ldap_tls && tls_checkpeer == "no")
		    {
			// yes/no question
			if (Popup::YesNo (_("The security connection is enabled, but server certificate verification is disabled.
Enable certificate checks now?")))
			    Ldap::tls_checkpeer	= "yes";
		    }
		    // check if user changed part of imported settings (#252094)
		    if (start && Stage::cont () && size (Ldap::initial_defaults) > 0 &&
			Ldap::create_ldap &&
			server != Ldap::initial_defaults["ldap_server"]:"" &&
			base_dn != Ldap::initial_defaults["ldap_domain"]:"" &&
			Ldap::bind_dn == Ldap::initial_defaults["bind_dn"]:"" &&
			!issubstring (Ldap::bind_dn, base_dn))
		    {
			y2warning ("Server and base DN changed but bind_dn remains imported -> disabling LDAP objects creation...");
			Ldap::create_ldap	= false;
		    }
		}
		Ldap::SetBaseDN (base_dn);
		Ldap::start	= start;
		Ldap::server	= server;
		Ldap::ldap_tls	= ldap_tls;
		Ldap::_start_autofs = autofs;
		Ldap::login_enabled = login_enabled;
		Ldap::mkhomedir = mkhomedir;
		Ldap::modified = true;
	    }
	}
	return result;
    }



/**
 * Configuration of advanced settings (how to get to config data on server)
 */
define symbol AdvancedConfigurationDialog () {

    map help_text	= $[
	`client		:
    // help text caption 1
    _("<p><b>Advanced LDAP Client Settings</b></p>") +

    // help text 1/3
    _("<p>Specify the search bases to use for specific maps (users, passwords, and groups) if they are different from the base DN. These values are
set to the nss_base_passwd, nss_base_shadow, and nss_base_group attributes
in /etc/ldap.conf file.</p>
")	+

    // help text 2/3
    _("<p><b>Password Change Protocol</b> refers to the pam_password attribute of the <tt>/etc/ldap.conf</tt> file. See <tt>man pam_ldap</tt> for the meaning of its values.</p>")	+

    // help text 3/3, %1 is attribute name
    sformat (_("<p>Set the type of LDAP groups to use.
The default value for <b>Group Member Attribute</b> is <i>%1</i>.</p>
"),
    "member") +

    _("<p>If secure connection requires certificate checking, you may specify where is your certificate file located. It is possible to enter either directory with certificates, or the explicit path to one certificate file.</p>") +

    // help text 7/9
    _("<p>Normally, the LDAP version 3 protocol is used. If you have
an LDAP server using protocol 2 (for example, OpenLDAP v1), activate
<b>LDAP Version 2</b>.</p>
"),


	`admin		:
    // help text caption 2
    _("<p><b>Access to Server</b></p>") +

    // help text 1/4
    _("<p>First, set <b>Configuration Base DN</b>.
It is the base for storing your configuration data, which is saved on the LDAP
server.</p>
") +

    // help text 2/4
    _("<p>To access the data stored on the server, enter the
<b>Administrator DN</b>.
You can enter the full DN (for example, cn=Administrator,dc=mydomain,dc=com) or just
the relative DN (for example, cn=Administrator). The LDAP base DN is appended automatically if the appropriate option is checked.</p>
") +

    // help text 3/4
    _("<p>To create the default configuration objects for LDAP users and groups,
check <b>Create Default Configuration Objects</b>. The objects are only created when they do not already exist.</p>
") +

    // help text 4/4
    _("<p>Press <b>Configure</b> to configure settings stored on the
LDAP server. You will be asked for the password if you are not connected yet or
have changed your configuration.</p>
") +

    // help text caption 3
    _("<p><b>Home Directories</b></p>") +

    // help text 1/1
    _("<p>If home directories of users should be stored on this machine,
check the appropriate option. Changing this value does not cause any direct
action.  It is only information for the YaST users module, which can manage
user home directories.</p>
") +

    // password policy help text caption
    _("<p><b>Password Policy</b></p>") +

    // password policy help
    _("<p>Configure the selected password policy with <b>Edit</b>. Use <b>Add</b> to add a new password policy. The configuration is only possible,\n  if the password policies are already enabled on the LDAP server.</p>")
    ];

    string bind_dn		= Ldap::bind_dn;
    string base_dn		= Ldap::GetBaseDN ();
    boolean file_server		= Ldap::file_server;
    string member_attribute	= Ldap::member_attribute;
    string base_config_dn	= Ldap::GetMainConfigDN();
    boolean create_ldap		= Ldap::create_ldap;
    boolean append_base	= (bind_dn != "" && issubstring (bind_dn, base_dn));
    string nss_base_passwd	= Ldap::nss_base_passwd;
    string nss_base_shadow	= Ldap::nss_base_shadow;
    string nss_base_group	= Ldap::nss_base_group;
    string pam_password		= Ldap::pam_password;
    boolean ldap_v2		= Ldap::ldap_v2;
    string tls_cacertdir	= Ldap::tls_cacertdir;
    string tls_cacertfile	= Ldap::tls_cacertfile;

    list<term>member_attributes	= [
	`item (`id("member"), "member", member_attribute == "member"),
	`item (`id("uniqueMember"), "uniqueMember", member_attribute == "uniqueMember")
    ];
    if (member_attribute != "member" && member_attribute != "uniqueMember")
    {
	member_attributes = add (member_attributes,
	    `item (`id(member_attribute), member_attribute, true));
    }

    // propose some good default
    if (base_config_dn == "")
	base_config_dn = sformat ("ou=ldapconfig,%1", base_dn);

    list pam_password_items	= [
	"ad",
	"crypt",
	"clear",
	"clear_remove_old",
	"exop",
	"exop_send_old",
	"md5",
	"nds",
	"racf"
    ];
    pam_password_items	= sort (maplist (
	string it, (list<string>) union (pam_password_items, [pam_password]),``(
	    `item (`id (it), it, it == pam_password)
	))
    );

    list ppolicy_list	= [];

    boolean ppolicies_enabled	= false;
    map<string,map> ppolicies	= $[];
    map<string,map> ppolicies_orig= $[];
    list<string> ppolicies_deleted	= []; // list of DN

    // read the list of pwdpolicy objects under base_config_dn
    void read_ppolicies () {

	if (base_dn == "") return;

	if (Ldap::ldap_initialized || true == SCR::Execute (.ldap, $[
	    "hostname"	: Ldap::GetFirstServer (Ldap::server),
	    "port"	: Ldap::GetFirstPort (Ldap::server),
	    "version"	: Ldap::ldap_v2 ? 2 : 3,
	    "use_tls"	: Ldap::ldap_tls ? "yes" : "no"
	    ])
	)
	{
	    ppolicies_enabled	= (boolean) SCR::Execute (.ldap.ppolicy, $[
		"hostname"	: Ldap::GetFirstServer (Ldap::server),
		"bind_dn"	: Ldap::GetBaseDN ()
	    ]);

	    list schemas = (list)SCR::Read (.ldap.search, $[
		"base_dn":  "",
		"attrs":    [ "subschemaSubentry" ],
		"scope":    0,
	    ]);
	    string schema_dn = schemas[0,"subschemaSubentry",0]:"";
	    if (schemas != nil && schema_dn != "" &&
		SCR::Execute (.ldap.schema, $[ "schema_dn": schema_dn ])== true)
	    {
		map<string,map> pp = (map<string,map>) SCR::Read (.ldap.search,
		$[
		    "base_dn"		: base_dn,
		    "filter"		: "objectClass=pwdPolicy",
		    "scope"		: 2,
		    "map"		: true,
		    "not_found_ok"	: true
		]);
		if (pp != nil)
		{
		    ppolicies	= pp;
		    ppolicies_orig	= ppolicies;
		}
	    }
	}
	// TODO re-read is not supported, is it correct?
	foreach (string dn, map ppolicy, Ldap::ppolicies, {
	    if (ppolicy["modified"]:"" == "deleted" && haskey (ppolicies, dn))
		ppolicies	= remove (ppolicies, dn);
	    else if (ppolicy["modified"]:"" == "added")
		ppolicies[dn]	= ppolicy;
	    else ppolicies[dn]	= union (ppolicies[dn]:$[], ppolicy);
	});
    }

    list<term> tabs	= [
	// tab label
	`item(`id(`client), _("C&lient Settings"), true),
	// tab label
	`item(`id(`admin), _("Ad&ministration Settings")),
    ];

    term contents = `VBox (
	`DumbTab (`id(`tabs), tabs,
	    `ReplacePoint(`id(`tabContents ), `VBox (`Empty ())))
    );
    boolean has_tabs	= true;
    if (!UI::HasSpecialWidget (`DumbTab))
    {
	has_tabs	= false;
	term tabbar	= `HBox ();
	foreach (term it, tabs, {
	    string label = it[1]:"";
	    tabbar = add (tabbar,`PushButton (it[0]:`id(label), label));
	});
	contents = `VBox (`Left(tabbar),
	    `Frame ("", `ReplacePoint(`id(`tabContents), `Empty ()))
	);
    }

    define void set_client_term () {

	term cont = `Top (`HBox(`HSpacing (5), `VBox(
	    `VSpacing(0.4),
	    // frame label
	    `Frame (_("Naming Contexts"), `HBox(
	    `HSpacing (1), `VBox(
		`VSpacing(0.4),
		`HBox (
		    `InputField (`id (`nss_base_passwd), `opt (`hstretch),
			// textentry label
			_("&User Map"), nss_base_passwd),
		    `VBox (
			`Label (""),
			// button label
			`PushButton (`id(`br_passwd), _("&Browse"))
		    )
		),
		`HBox (
		    `InputField (`id (`nss_base_shadow), `opt (`hstretch),
			// textentry label
			_("&Password Map"), nss_base_shadow),
		    `VBox (
			`Label (""),
			// button label
			`PushButton (`id(`br_shadow), _("Brow&se"))
		    )
		),
		`HBox (
		    `InputField (`id (`nss_base_group), `opt (`hstretch),
			// textentry label
			_("&Group Map"), nss_base_group),
		    `VBox (
			`Label (""),
			// button label
			`PushButton (`id(`br_group), _("Bro&wse"))
		    )
		),
		`VSpacing(0.4)
	    ),
	    `HSpacing (1)
	    )),
	    `VSpacing (0.4),
	    `ComboBox (`id (`pam_password), `opt(`notify,`hstretch,`editable),
		// combobox label
		_("Passwor&d Change Protocol"), pam_password_items),
	    `VSpacing(0.4),
	    `ComboBox (`id (`group_style), `opt (`notify, `hstretch),
		// combobox label
		_("Group Member &Attribute"), member_attributes),
	    // check box label
	    `VSpacing(0.4),
	    `HBox (
	      `HWeight (1, `HBox (
		`InputField (`id (`tls_cacertdir), `opt (`hstretch), _("Certificate Directory"),
		    tls_cacertdir
		),
		`VBox (
		    `Label (""),
		    // button label
		    `PushButton (`id(`br_tls_cacertdir), _("B&rowse"))
		)
	      )), `HWeight (1, `HBox (
		`InputField (`id (`tls_cacertfile), `opt (`hstretch), _("CA Certificate File"),
		    tls_cacertfile
		),
		`VBox (
		    `Label (""),
		    // button label
		    `PushButton (`id(`br_tls_cacertfile), _("Brows&e"))
		)
	      ))
	    ),
	    `VSpacing(0.2),
	    `Left (`CheckBox (`id(`ldapv), _("LDAP &Version 2"), ldap_v2))
	), `HSpacing (5)));

	UI::ReplaceWidget (`tabContents, cont);
	if (has_tabs)
	    UI::ChangeWidget (`id (`tabs), `CurrentItem, `client);
    }

    define void set_admin_term () {

	term cont = `HBox (`HSpacing (5), `VBox(
	    `VSpacing (0.4),
	    `HBox (
		    `InputField (`id (`base_config_dn), `opt (`hstretch),
			// textentry label
			_("Configuration &Base DN"), base_config_dn),
		    `VBox (
			`Label (""),
			// button label
			`PushButton (`id(`br), `opt(`key_F6), _("Bro&wse"))
		    )
	    ),
	    `VSpacing(0.4),
	    `HBox (
		`InputField (`id (`bind_dn),  `opt (`hstretch),
		    // textentry label
		    _("Administrator &DN"), bind_dn),
		`VBox (
		    `Label (""),
		    // checkbox label
		    `CheckBox (`id(`append),_("A&ppend Base DN"), append_base)
		)
	    ),
	    `VSpacing (0.4),
	    `Left (`CheckBox (`id(`create_ldap),
		    // checkbox label
		    _("Crea&te Default Configuration Objects"), create_ldap)),
	    `VSpacing(0.4),
	    `Left (`CheckBox (`id(`file_server),
		// checkbox label
		_("&Home Directories on This Machine"),
		file_server)
	    ),
	    `VSpacing(0.5),
	    `Right (`PushButton (`id(`configure),
		    // pushbutton label
		    _("Configure User Management &Settings..."))),
	    `VSpacing (),
	    `Table (`id (`ppolicy_table), `opt(`notify), `header (
		// table header
		_("Password Policy")),
		maplist (string dn, map pp, ppolicies, ``(`item (`id (dn), dn)))
	    ),
	    `HBox (
		`PushButton (`id (`add), Label::AddButton ()),
		`PushButton (`id (`edit), Label::EditButton ()),
		`PushButton (`id (`delete), Label::DeleteButton ()),
		`HStretch ()
	    ),
	    `VSpacing(0.4)
	), `HSpacing (5));

	UI::ReplaceWidget (`tabContents, cont);
	if (has_tabs)
	    UI::ChangeWidget (`id (`tabs), `CurrentItem, `admin);

	// in autoyast-config mode, don't attach to server...
	if (Mode::config ())
	    UI::ChangeWidget (`id(`configure), `Enabled, false);

	foreach (symbol s, [ `ppolicy_table, `add, `edit, `delete ], {
	    UI::ChangeWidget (`id (s), `Enabled, ppolicies_enabled);
	});
    }


    // dialog label
    Wizard::SetContentsButtons (_("Advanced Configuration"), contents,
	help_text[`client]:"", Label::CancelButton(), Label::OKButton());

    Wizard::HideAbortButton ();

    any result	= `notnext;
    symbol current	= `client;

    set_client_term ();
    read_ppolicies ();

    while (true)
    {
	result = UI::UserInput ();
	if (result == `cancel && ReallyAbort () != `abort)
	    result = `not_next;

	if (result == `back || result == `cancel)
	    break;

	// 1. get the data from dialogs
	if (current == `client)
	{
	    member_attribute =(string)UI::QueryWidget(`id(`group_style),`Value);
	    nss_base_passwd	= (string)
		UI::QueryWidget(`id(`nss_base_passwd),`Value);
	    nss_base_shadow	= (string)
		UI::QueryWidget(`id(`nss_base_shadow),`Value);
	    nss_base_group	= (string)
		UI::QueryWidget(`id(`nss_base_group),`Value);
	    pam_password = (string) UI::QueryWidget(`id(`pam_password), `Value);

	    tls_cacertfile	= (string) UI::QueryWidget(`id(`tls_cacertfile), `Value);
	    tls_cacertdir	= (string) UI::QueryWidget(`id(`tls_cacertdir), `Value);
	    ldap_v2		= (boolean) UI::QueryWidget(`id(`ldapv), `Value);
	}
	if (current == `admin)
	{
	    bind_dn = (string) UI::QueryWidget(`id(`bind_dn), `Value);
	    base_config_dn=(string)UI::QueryWidget(`id(`base_config_dn),`Value);
	    file_server = (boolean)UI::QueryWidget(`id(`file_server), `Value);
	    create_ldap = (boolean) UI::QueryWidget(`id(`create_ldap),`Value);
	    append_base = (boolean) UI::QueryWidget (`id(`append), `Value);
	    if (append_base && ! issubstring (bind_dn, base_dn) && bind_dn != "")
	    {
		bind_dn = sformat ("%1,%2", bind_dn, base_dn);
		UI::ChangeWidget (`id(`bind_dn), `Value, bind_dn);
	    }
	}

	// 2. switch the tabs
	if (result == `client || result == `admin)
	{
	    current = (symbol) result;
	    Wizard::SetHelpText (help_text[current]:"");
	    if (result == `client)
		set_client_term ();
	    else
		set_admin_term ();
	}

	// 3. other events
	if (is (result, symbol) &&
	    contains ([`br, `br_passwd, `br_shadow, `br_group ], (symbol)result))
	{
	    string dn = Ldap::ldap_initialized ?
		LdapPopup::BrowseTree (base_dn) :
		LdapPopup::InitAndBrowseTree (base_dn, $[
		    "hostname"	: Ldap::GetFirstServer (Ldap::server),
		    "port"	: Ldap::GetFirstPort (Ldap::server),
		    "version"	: Ldap::ldap_v2 ? 2 : 3,
		    "use_tls"	: Ldap::ldap_tls ? "yes" : "no"
		]);
	    if (dn != "")
	    {
		map br2entry	= $[
		    `br		: `base_config_dn,
		    `br_passwd	: `nss_base_passwd,
		    `br_shadow	: `nss_base_shadow,
		    `br_group	: `nss_base_group
		];
		UI::ChangeWidget (`id(br2entry[result]:nil), `Value, dn);
	    }
	}
	if (result == `br_tls_cacertdir)
	{
	    string dir = UI::AskForExistingDirectory (tls_cacertdir, _("Choose the directory with certificates"));
	    if (dir != nil)
	    {
		tls_cacertdir	= dir;
		UI::ChangeWidget (`id (`tls_cacertdir), `Value, dir);
	    }
	}
	if (result == `br_tls_cacertfile)
	{
	    string file = UI::AskForExistingFile (tls_cacertfile, "*.pem *.crt", _("Choose the certificate file"));
	    if (file != nil)
	    {
		tls_cacertfile	= file;
		UI::ChangeWidget (`id (`tls_cacertfile), `Value, file);
	    }
	}
	if (result == `add)
	{
	    string suffix	= base_dn;
	    UI::OpenDialog ( `opt(`decorated), `HBox(
		`HSpacing(1),
		`VBox(
		    // InputField label
		    `InputField (`id (`cn),  `opt (`hstretch),
			_("Name of Password Policy Object")),
		    `ReplacePoint (`id (`rp_suf), `HBox (
			// text label,suffix will follow in next label
			`Label (`id (`suffix_label), _("Suffix:")),
			`Label (`id (`suffix), base_dn),
			// pushbutton label
			`PushButton (`id (`br_suf), _("Change Suffix"))
		    )),
		    `HBox (
			`PushButton(`id(`ok),`opt(`default,`key_F10),Label::OKButton()),
			`PushButton(`id(`cancel),`opt (`key_F9), Label::CancelButton())
		    )
		),
		`HSpacing(1)
	    ));
	    UI::SetFocus (`id (`cn));
	    any ret		= nil;
	    string new_dn	= "";
	    while (true)
	    {
		ret	= UI::UserInput ();
		if (ret == `cancel)
		    break;
		if (ret == `br_suf)
		{
		    string suf	= LdapPopup::InitAndBrowseTree (base_dn, $[
			"hostname"	: Ldap::GetFirstServer (Ldap::server),
			"port"		: Ldap::GetFirstPort (Ldap::server),
			"version"	: Ldap::ldap_v2 ? 2 : 3,
			"use_tls"	: Ldap::ldap_tls ? "yes" : "no"
		    ]);
		    if (suf != "")
			UI::ReplaceWidget (`id (`rp_suf), `HBox (
			    // text label,suffix will follow in next label
			    `Label (`id (`suffix_label), _("Suffix:")),
			    `Label (`id (`suffix), suf),
			    // pushbutton label
			    `PushButton (`id (`br_suf), _("Change Suffix"))
			));
		}
		if (ret == `ok)
		{
		    string cn		= (string) UI::QueryWidget (`id (`cn), `Value);
		    if (cn == "") break;
		    string suffix	= (string) UI::QueryWidget (`id (`suffix), `Value);
		    new_dn		= sformat ("cn=%1,%2", cn, suffix);
		    if (haskey (ppolicies, new_dn))
		    {
			Popup::Error (sformat (_("The Policy \'%1\' already exists.
Please select another one."), new_dn));
			continue;
		    }
		    break;
		}
	    }
	    UI::CloseDialog ();
	    if (ret == `ok && new_dn != "")
	    {
		map new	= LdapPopup::PasswordPolicyDialog ($["dn": new_dn ]);
		if (new != nil)
		{
		    ppolicies[new_dn]	= new;
		    UI::ChangeWidget (`id (`ppolicy_table), `Items, 
			maplist (string dn, map pp, ppolicies, ``(`item (`id (dn), dn)))
		    );
		    UI::ChangeWidget (`id (`edit), `Enabled, size (ppolicies) > 0);
		    UI::ChangeWidget (`id (`delete), `Enabled, size (ppolicies) > 0);
		}
	    }
	}
	if (result == `edit || result == `ppolicy_table)
	{
	    string dn	= (string) UI::QueryWidget (`id (`ppolicy_table), `CurrentItem);
	    map changes	= LdapPopup::PasswordPolicyDialog (ppolicies[dn]:$[]);
	    if (changes != nil)
	    {
		ppolicies[dn]	= union (ppolicies[dn]:$[], changes);
	    }
	}
	if (result == `delete)
	{
	    string dn   = (string) UI::QueryWidget (`id (`ppolicy_table), `CurrentItem);
	    ppolicies	= remove (ppolicies, dn);
	    ppolicies_deleted	= (list<string>) union (ppolicies_deleted, [dn]);
	    UI::ChangeWidget (`id (`ppolicy_table), `Items,
		maplist (string dn, map pp, ppolicies, ``(`item (`id (dn), dn)))
	    );
	    UI::ChangeWidget (`id (`edit), `Enabled, size (ppolicies) > 0);
	    UI::ChangeWidget (`id (`delete), `Enabled, size (ppolicies) > 0);
	}

	if (result == `next || result == `configure)
	{
	    if (result == `configure && bind_dn == "")
	    {
		// error popup label
                Report::Error(_("Enter the DN used for binding to the LDAP server."));
		UI::SetFocus (`id (`bind_dn));
		continue;
	    }

	    if (result == `configure && base_config_dn == "")
	    {
		// error popup label
                Report::Error(_("Enter the configuration base DN."));
		UI::SetFocus (`id (`base_config_dn));
		continue;
	    }
	    foreach (string dn, map ppolicy, ppolicies, {
		// new ppolicy
		if (!haskey (ppolicies_orig, dn))
		{
		    ppolicy["modified"]		= "added";
		    ppolicy["pwdAttribute"]	= "userPassword";
		    ppolicy["objectClass"]	= ["pwdPolicy", "namedObject"];
		    ppolicy["cn"]		= get_cn (dn);
		    Ldap::ppolicies[dn]	= ppolicy;

		}
		else
		{
		    map pp	= $[];
		    foreach (string a, any val, (map<string,any>) ppolicy, {
			if (val != ppolicies_orig[dn,a]:nil)
			    pp[a]	= val;
		    });
		    if (pp != $[])
		    {
			pp["modified"]		= "edited";
			Ldap::ppolicies[dn]	= pp;
		    }
		}
	    });
	    // deleted ppolicies
	    foreach (string dn, ppolicies_deleted, {
		map pp	= Ldap::ppolicies[dn]:$[];
		if (pp["modified"]:"" == "added")
		{
		    Ldap::ppolicies	= remove (Ldap::ppolicies, dn);
		}
		else if (haskey (ppolicies_orig, dn))
		{
		    pp["modified"]	= "deleted";
		    Ldap::ppolicies[dn]	= pp;
		}
	    });

	    if (Ldap::GetMainConfigDN() != base_config_dn	||
		Ldap::bind_dn != bind_dn			||
		Ldap::file_server != file_server		||
		Ldap::member_attribute != member_attribute	||
		Ldap::create_ldap != create_ldap		||
		Ldap::pam_password != pam_password		||
		Ldap::nss_base_passwd != nss_base_passwd	||
		Ldap::nss_base_group != nss_base_group		||
		Ldap::nss_base_shadow != nss_base_shadow	||
		Ldap::ldap_v2 != ldap_v2 ||
		Ldap::tls_cacertdir != tls_cacertdir ||
		Ldap::tls_cacertfile != tls_cacertfile
	    )
	    {
		Ldap::bind_dn		= bind_dn;
		Ldap::base_config_dn	= base_config_dn;
		Ldap::file_server	= file_server;
		Ldap::member_attribute	= member_attribute;
		Ldap::create_ldap	= create_ldap;
		Ldap::pam_password	= pam_password;
		Ldap::nss_base_passwd	= nss_base_passwd;
		Ldap::nss_base_group	= nss_base_group;
		Ldap::nss_base_shadow	= nss_base_shadow;
		Ldap::ldap_v2		= ldap_v2;
		Ldap::tls_cacertdir	= tls_cacertdir;
		Ldap::tls_cacertfile	= tls_cacertfile;
		Ldap::modified		= true;
	    }
	    break;
	}
    }
    return (symbol) result;
}

/**
 * Initialize connection to LDAP server, bind and read the settings.
 * Everything is done before entering the Module Configuration Dialog.
 */
define symbol LDAPReadDialog () ``{

    string msg		= "";
    boolean read_now	= false;

    if (!Ldap::bound || Modified())
    {
	if (!Ldap::bound || Ldap::modified)
	{
	    // re-init/re-bind only when server information was changed (#39908)
	    if (!Ldap::bound || Ldap::old_server != Ldap::server ||
		 Ldap::BaseDNChanged ())
	    {
		msg = Ldap::LDAPInitWithTLSCheck ($[]);
		if (msg != "")
		{
		    Ldap::LDAPErrorMessage ("init", msg);
		    return `back;
		}
	    }

	    if (!Ldap::bound || Ldap::old_server != Ldap::server)
	    {
		// Ldap::bind_pass might exist from server proposal...
		if (Stage::cont () && Ldap::bind_pass != nil)
		{
		    msg = Ldap::LDAPBind (Ldap::bind_pass);
		    if (msg != "")
		    {
			Ldap::LDAPErrorMessage ("bind", msg);
			Ldap::bind_pass = Ldap::LDAPAskAndBind (true);
		    }
		}
		else
		{
		    Ldap::bind_pass = Ldap::LDAPAskAndBind (true);
		}
		if (Ldap::bind_pass == nil)
		    return `back;

		read_now	= true;

		msg = Ldap::InitSchema ();
		if (msg != "") Ldap::LDAPErrorMessage ("schema", msg);
	    }
	}
	if (!Ldap::CheckBaseConfig (Ldap::base_config_dn))
	    return `back;
	if (read_now || (Ldap::modified && !Ldap::ldap_modified) ||
	    (Ldap::ldap_modified && Popup::AnyQuestion( Popup::NoHeadline(),
// yes/no popup
_("If you reread settings from the server,
all changes will be lost. Really reread?
"), Label::YesButton(), Label::NoButton(), `focus_no))
	)
	{
	    msg = Ldap::ReadConfigModules ();
	    if (msg != "") Ldap::LDAPErrorMessage ("read", msg);

	    msg = Ldap::ReadTemplates ();
	    if (msg != "") Ldap::LDAPErrorMessage ("read", msg);

	    Ldap::ldap_modified = false;
	}
	Ldap::bound = true;
    }
    return `next;
}

/**
 * Dialog for configuration one object template
 */
define map<string,any> TemplateConfigurationDialog (map templ) {

    // help text 1/3
    string help_text = _("<p>Here, configure the template used for
creating new objects (like users or groups).</p>
") +

    // help text 2/3
    _("<p>Edit the template attribute values with <b>Edit</b>.
Changing the <b>cn</b> value renames the template.</p>
") +

    // help text 3/3
    _("<p>The second table contains a list of <b>default values</b>, used
for new objects. Modify the list by adding new values and editing or
removing current ones.</p>
");

    string template_dn = Ldap::current_template_dn;

    list table_items = [];
    map<string,any> template = (map<string,any>) eval (templ);

    // helper function converting list value to string
    define string to_table (string attr, list<string> val) ``{

	if (Ldap::SingleValued (attr) || attr == "cn")
	    return val[0]:"";
	else if (contains (
	    ["susesecondarygroup", "susedefaulttemplate"], tolower(attr)))
	    return mergestring (val, " ");
	else
	    return mergestring (val, ",");
    }

    foreach (string attr, any value, template, ``{
	any val = value;
	// do not show internal attributes
	if (contains (["susedefaultvalue", "default_values", "objectclass",
	"modified", "old_dn"], tolower (attr)))
	    return;
	if (is (value, list))
	    val = to_table (attr, (list<string>) val);
	table_items = add (table_items, `item (`id(attr), attr, val));
    });

    list<term> default_items = [];
    map<string,string> default_values = template["default_values"]:$[];
    foreach (string attr, string value, default_values, ``{
	default_items = add (default_items, `item (`id(attr), attr, value));
    });

    term contents = `HBox(`HSpacing (1.5), `VBox(
        `VSpacing(0.5),
	`Table(`id(`table), `opt(`notify), `header(
	    // table header 1/2
	    _("Attribute"),
	    // table header 2/2
	    _("Value")),
	    table_items),
        `HBox (
            `PushButton(`id(`edit), Label::EditButton()),
	    `HStretch()
	),
	// label (table folows)
	`Left(`Label (_("Default Values for New Objects"))),
	`Table(`id(`defaults), `opt(`notify), `header(
	    // table header 1/2
	    _("Attribute of Object"),
	    // table header 2/2
	    _("Default Value")),
	    default_items),
        `HBox (
	    // button label (with non-default shortcut)
            `PushButton (`id(`add_dfl), `opt(`key_F3), _("A&dd")),
	    // button label
            `PushButton (`id(`edit_dfl), `opt(`key_F4), _("&Edit")),
            `PushButton(`id(`delete_dfl), `opt(`key_F5), Label::DeleteButton()),
            `HStretch()
	),
	`VSpacing (0.5)
	),
	`HSpacing (1.5));

    Wizard::OpenNextBackDialog ();
    // dialog label
    Wizard::SetContentsButtons(_("Object Template Configuration"),
	contents, help_text, Label::CancelButton(), Label::OKButton());
    Wizard::HideAbortButton();

    if (size (table_items) > 0)
    {
	UI::SetFocus (`id(`table));
    }
    UI::ChangeWidget (`id(`edit_dfl), `Enabled, default_items != []);
    UI::ChangeWidget (`id(`delete_dfl), `Enabled, default_items != []);

    any result = nil;
    while (true)
    {
	result		= UI::UserInput ();
	string attr	= (string)UI::QueryWidget (`id(`table), `CurrentItem);

	// edit attribute
	if (result == `edit || result == `table)
	{
	    if (attr == nil)
		continue;
	    list<string> value	= template [attr]:[];
	    list offer		= [];
	    list conflicts	= [];
	    if (tolower (attr) == "susesecondarygroup")
		offer		= Ldap::GetGroupsDN (Ldap::GetBaseDN());
	    if (tolower (attr) == "susenamingattribute")
	    {
		list classes	= Ldap::GetDefaultObjectClasses (template);
		offer		= Ldap::GetObjectAttributes (classes);
	    }
	    if (attr == "cn")
	    {
		string base = issubstring (template_dn, ",") ?
		    substring (template_dn, search (template_dn,",")+1) : "";
		foreach (string dn, Ldap::ReadDN (base, ""), ``{
		    if (substring (dn,0,3) == "cn=")
			conflicts = add (conflicts, get_cn (dn));
		});
	    }
	    value = LdapPopup::EditAttribute ($[
		"attr"		: attr,
		"value"		: value,
		"conflicts"	: conflicts,
		"single"	: Ldap::SingleValued (attr) || attr == "cn",
		"offer"		: offer,
		"browse"	: tolower (attr) == "susesecondarygroup"
	    ]);

	    if (value == template[attr]:[])
		continue;
	    UI::ChangeWidget (`id(`table),`Item(attr,1), to_table(attr, value));
	    template [attr] = value;
	}
	// add default value
	if (result == `add_dfl)
	{
	    list conflicts =
		maplist (string attr, string val, default_values,``(attr));
	    list classes = Ldap::GetDefaultObjectClasses (template);
	    list available = Ldap::GetObjectAttributes (classes);
	    // filter out objectclass
	    map dfl = LdapPopup::AddDefaultValue (sort (available),
		add (conflicts, "objectClass"));
	    if (dfl["value"]:"" == "")
		continue;
	    string attr = dfl["attr"]:"";
	    default_values [attr] = dfl["value"]:"";
	    default_items = add (default_items,
		`item (`id(attr), attr, dfl["value"]:""));
	    UI::ChangeWidget (`id(`defaults), `Items, default_items);
	    UI::ChangeWidget (`id(`edit_dfl), `Enabled, default_items != []);
	    UI::ChangeWidget (`id(`delete_dfl), `Enabled, default_items != []);
	}
	// edit default value
	if (result == `edit_dfl || result == `defaults)
	{
	    attr	= (string)UI::QueryWidget (`id(`defaults),`CurrentItem);
	    if (attr == nil)
		continue;
	    string value	= default_values [attr]:"";
	    list l_value	= LdapPopup::EditAttribute ($[
		"attr"		: attr,
		"value"		: [value],
		"single"	: true,
	    ]);
	    if (l_value [0]:"" == value)
		continue;
	    value = l_value[0]:"";
	    UI::ChangeWidget (`id(`defaults), `Item (attr, 1), value);
	    default_values [attr] = value;
	}
	// delete default value
	if (result == `delete_dfl)
	{
	    attr	= (string)UI::QueryWidget (`id(`defaults),`CurrentItem);
	    if (attr == nil)
		continue;
	    // yes/no popup, %1 is name
	    if (!Popup::YesNo (sformat (_("Really delete default attribute \"%1\"?"), attr)))
		continue;
	    default_values = remove (default_values, attr);
	    default_items = filter (term it, default_items, ``(
		it[1]:"" != attr));
	    UI::ChangeWidget (`id(`defaults), `Items, default_items);
	    UI::ChangeWidget (`id(`edit_dfl), `Enabled, default_items != []);
	    UI::ChangeWidget (`id(`delete_dfl), `Enabled, default_items != []);
	}
	if (is(result,symbol) && contains ([`back, `cancel, `abort], (symbol)result))
	    break;
	if (result == `next)
	{
	    boolean cont = false;

	    // check the template required attributes...
	    foreach (string oc, template["objectClass"]:[], ``{
		if (cont) return;
		foreach (string attr, Ldap::GetRequiredAttributes (oc), ``{
		    any val = template[attr]:nil;
		    if (!cont && val == nil || val == [] || val == "") {
			//error popup, %1 is attribute name
			Popup::Error (sformat (_("The \"%1\" attribute is mandatory.
Enter a value."), attr));
			UI::SetFocus (`id(`table));
			cont = true;
		    }
		});
	    });
	    if (cont) continue;
	    template ["default_values"] = default_values;
	    break;
	}
    }
    Wizard::CloseDialog ();
    return template;
}

/**
 * Dialog for configuration of one "configuration module"
 */
define symbol ModuleConfigurationDialog () ``{

    // helptext 1/4
    string help_text = _("<p>Here, manage the configuration stored in LDAP directory.</p>") +

    // helptext 2/4
    _("<p>Each configuration set is called a \"configuration module.\" If there
is no configuration module in the provided location (base configuration),
create one with <b>New</b>. Delete the current module
using <b>Delete</b>.</p>
") +

    // helptext 3/4
    _("<p>Edit the values of attributes in the table with <b>Edit</b>.
Some values have special meanings, for example, changing the <b>cn</b> value renames the
current module.</p>
") +

    // helptext 4/4
    _("<p>To configure the default template of the current module,
click <b>Configure Template</b>.
</p>
");

    string current_dn		= Ldap::current_module_dn;
    map  modules_attrs_items	= $[]; // map of list (table items), index is cn
    map<string,map<string,any> > modules	=
	(map<string,map<string,any> >) Ldap::GetConfigModules ();
    map<string,map<string,any> > templates	=
	(map<string,map<string,any> >) Ldap::GetTemplates ();
    list<string> names		= [];
    list<string> templates_dns	= maplist (string dn, map t, templates, ``(dn));

    /**
     * Helper for creating table items in ModuleConfiguration Dialog
     */
    define list create_attrs_items (string cn) ``{

	list attrs_items = [];
	string dn	= get_dn (cn);
	if (!haskey (modules, dn))
	    dn		= tolower (dn);
	foreach (string attr, any value, modules[dn]:$[], ``{
	    any val = value;
	    if (contains (["objectclass", "modified", "old_dn"], tolower(attr)))
		return;
	    if (is (value, list))
	    {
		list lvalue	= (list) value;
		if (Ldap::SingleValued (attr) || attr == "cn")
		    val = lvalue[0]:"";
		else
		    val = mergestring ((list<string>)value, ",");
	    }
	    attrs_items = add (attrs_items, `item(`id(attr), attr, val));
	});

	return attrs_items;
    }

    foreach (string dn, map mod, modules, ``{
	string cn = get_string (mod, "cn");
	if (cn == "")
	    return;
	names = add (names, cn);
	// attributes for table
	modules_attrs_items [cn] = create_attrs_items (cn);
	if (current_dn == "")
	    current_dn = dn;
    });
    string current_cn = modules [current_dn, "cn",0]:get_cn (current_dn);

    /**
     * Helper for updating widgets in ModuleConfiguration Dialog
     */
    define void replace_module_names () ``{

	list modules_items = []; // list of module names
	foreach (string cn, names, ``{
	    if (tolower (cn) == tolower (current_cn))
		modules_items = add (modules_items, `item(`id(cn), cn, true));
	    else
		modules_items = add (modules_items, `item(`id(cn), cn));
	});
	UI::ReplaceWidget (`id(`rp_modnames),
	    `Left(`ComboBox (`id (`modules), `opt (`notify),
		// combobox label
		_("Configuration &Module"), modules_items)));
	boolean ena = names != [];
	UI::ChangeWidget (`id(`delete), `Enabled, ena);
	UI::ChangeWidget (`id(`edit), `Enabled, ena);
	UI::ChangeWidget (`id(`modules), `Enabled, ena);
    }

    /**
     * Helper for updating widgets in ModuleConfiguration Dialog
     */
    define void replace_templates_items () ``{

	list items = maplist (string dn,
	    modules[current_dn,"suseDefaultTemplate"]:[],
	    ``(`item (`id(dn), dn))
	);
	UI::ReplaceWidget (`id(`rp_templs),
	    `PushButton (`id(`templ_pb), `opt(`key_F7),
		// button label
		_("C&onfigure Template")));
	UI::ChangeWidget (`id(`templ_pb), `Enabled, items != []);
    }

    term contents = `HBox(`HSpacing (1.5), `VBox(
        `VSpacing(0.5),
	`HBox (
	    `ReplacePoint (`id(`rp_modnames), `Empty()),
	    `VBox (
		`Label (""),
		`PushButton (`id (`new), `opt(`key_F3), Label::NewButton())
	    ),
	    `VBox (
		`Label (""),
		`PushButton (`id (`delete), `opt(`key_F5),Label::DeleteButton())
	    )
	),
        `VSpacing(0.5),
	`Table(`id(`table), `opt(`notify), `header(
	    // table header 1/2
	    _("Attribute"),
	    // table header 2/2
	    _("Value")),
	    modules_attrs_items[current_cn]:[]),
        `HBox (
            `PushButton(`id(`edit), `opt(`key_F4), Label::EditButton()),
            `HStretch(),
	    `ReplacePoint (`id(`rp_templs), `Empty ())
	),
	`VSpacing (0.5)
	),
	`HSpacing (1.5));

    // dialog label
    Wizard::SetContentsButtons(_("Module Configuration"),
            contents, help_text, Label::CancelButton(), Label::OKButton());
    Wizard::HideAbortButton ();

    if (size (modules_attrs_items[current_cn]:[]) > 0)
    {
	UI::SetFocus (`id(`table));
    }
    replace_templates_items ();
    replace_module_names ();

    // result could be symbol or string
    any result = nil;
    while (true)
    {
	result		= UI::UserInput ();
	string attr	= (string)UI::QueryWidget (`id(`table), `CurrentItem);

	// check the correctness of entry
	if (contains (modules [current_dn, "suseDefaultTemplate"]:[], result) ||
	    result == `next || result == `modules || result == `new)
	{
	    foreach (string oc, modules [current_dn, "objectClass"]:[], {
		foreach (string attr, Ldap::GetRequiredAttributes (oc), {
		    any val = modules [current_dn, attr]:nil;
		    if (val == nil || val == [] || val == "") {
			//error popup, %1 is attribute name
			Popup::Error (sformat (_("The \"%1\" attribute is mandatory.
Enter a value."), attr));
			UI::SetFocus (`id(`table));
			result = `notnext;
			continue;
		    }
		});
	    });
	}
	// change the focus to new module
	if (result == `modules)
	{
	    current_cn = (string)UI::QueryWidget (`id(`modules), `Value);
	    current_dn = get_dn (current_cn);
	    if (!haskey (modules, current_dn))
		current_dn	= tolower (current_dn);
	    UI::ChangeWidget (`id(`table), `Items,
		modules_attrs_items[current_cn]:[]);
	    replace_templates_items ();
	}
	// delete the module
	if (result == `delete)
	{
	    // yes/no popup, %1 is name
	    if (!Popup::YesNo (sformat (_("Really delete module \"%1\"?"), current_cn)))
		continue;
	    modules_attrs_items = remove (modules_attrs_items, current_cn);
	    if (modules [current_dn, "modified"]:"" != "added")
	    {
		modules [current_dn, "modified"] = "deleted";
	    }
	    names = filter (string n, names, ``(n != current_cn));
	    current_cn = names [0]:"";
	    current_dn = get_dn (current_cn);
	    if (!haskey (modules, current_dn))
		current_dn	= tolower (current_dn);
	    replace_module_names ();
	    replace_templates_items ();
	    UI::ChangeWidget (`id(`table), `Items,
		modules_attrs_items[current_cn]:[]);
	}
	// new module
	if (result == `new)
	{
	    list<string> available = Ldap::available_config_modules;
	    foreach (string dn, map mod, modules, ``{
		if (mod["modified"]:"" == "deleted")
		    return;
		foreach (string cl, mod["objectClass"]:[], {
		    available = filter (string c, available, ``(
			tolower (c) != tolower (cl)));
		});
	    });
	    if (available == [])
	    {
		// message
		Popup::Message (_("You currently have configuration modules
of each type, so you cannot add a new one."));
		continue;
	    }
	    // get new name and class
	    map new = LdapPopup::NewModule (available, names);
	    string cn = new ["cn"]:"";
	    if (cn == "")
		continue;
	    current_cn = cn;
	    current_dn = get_dn (current_cn);
	    if (!haskey (modules, current_dn))
		current_dn	= tolower (current_dn);
	    modules [current_dn] = Ldap::CreateModule (cn, new ["class"]:"");
	    names = add (names, cn);
	    modules_attrs_items [cn] = create_attrs_items (cn);
	    replace_module_names ();
	    replace_templates_items ();
	    UI::ChangeWidget (`id(`table), `Items,
		modules_attrs_items[current_cn]:[]);
	}
	// module attribute modification
	if (result == `edit || result == `table)
	{
	    if (attr == nil)
		continue;
	    list<string> value		= modules [current_dn, attr]:[];
	    list offer		= [];
	    list conflicts	= [];
	    if (attr == "cn")
		conflicts = names;
	    if (tolower (attr) == "susedefaulttemplate")
		offer		= templates_dns;
	    else if (tolower (attr) == "susepasswordhash")
		offer		= Ldap::hash_schemas;

	    value = LdapPopup::EditAttribute ($[
		"attr"		: attr,
		"value"		: value,
		"conflicts"	: conflicts,
		"single"	: Ldap::SingleValued (attr) || attr == "cn",
		"offer"		: offer,
		"browse"	:
		    // TODO function, that checks if value should be DN
		    (tolower (attr) == "susedefaultbase" ||
		     tolower (attr) == "susedefaulttemplate")
	    ]);

	    if (value == modules [current_dn, attr]:[]) //nothing was changed
		continue;
	    modules [current_dn, attr] = value;
	    modules_attrs_items [current_cn] = create_attrs_items (current_cn);
	    UI::ChangeWidget (`id(`table), `Items,
		modules_attrs_items[current_cn]:[]);
	    UI::ChangeWidget (`id(`table), `CurrentItem, attr);
	    if (attr == "cn" && value != [])
	    {
		string cn = value [0]:current_cn;
		modules_attrs_items[cn] = modules_attrs_items[current_cn]:[];
		modules_attrs_items = remove (modules_attrs_items, current_cn);
		if (modules [current_dn, "modified"]:"" != "added" &&
		    modules [current_dn, "modified"]:"" != "renamed")
		{
		    modules [current_dn, "modified"] = "renamed";
		    modules [current_dn, "old_dn"] = current_dn;
		}
		modules [ get_dn (cn) ] = modules [current_dn]:$[];
		if (tolower (get_dn (cn)) != tolower (current_dn))
		    modules = remove (modules, current_dn);
		names = filter (string n, names, ``(n != current_cn));
		names = add (names, cn);
		current_cn = cn;
		current_dn = get_dn (cn);
		replace_module_names ();
	    }
	    if (tolower (attr) == "susedefaulttemplate")
		replace_templates_items ();
	}
	// configure template
	if (result == `templ_pb)
	{
	    string template_dn = modules[current_dn,"suseDefaultTemplate",0]:"";
	    Ldap::current_template_dn = template_dn;
	    map template = (map) eval (templates [template_dn]:$[]);
	    // template not loaded, check DN:
	    if (template == $[])
	    {
		template = Ldap::CheckTemplateDN (template_dn);
		if (template == nil)
		    continue;
		else if (template == $[])
		{
		    if (!Ldap::ParentExists (template_dn))
			continue;
		    template = Ldap::CreateTemplate (get_cn (template_dn),
			modules [current_dn, "objectClass"]:[]);
		}
		templates_dns = add (templates_dns, template_dn);
	    }
	    templates [template_dn] = TemplateConfigurationDialog (template);
	    // check for template renaming
	    if (templates [template_dn, "cn"]:[] != template ["cn"]:[])
	    {
		string cn = get_string (templates [template_dn]:$[], "cn");
		string new_dn = get_new_dn (cn, template_dn);

		templates [new_dn] = templates [template_dn]:$[];
		if (new_dn != template_dn)
		    templates = remove (templates, template_dn);
		if (templates [new_dn, "modified"]:"" != "added")
		{
		    templates [new_dn, "modified"] = "renamed";
		    templates [new_dn, "old_dn"] = template_dn;
		}
		templates_dns = filter (string dn, templates_dns, ``(
		    dn != template_dn));
		templates_dns = add (templates_dns, new_dn);
		// update list of templates
		modules [current_dn, "suseDefaultTemplate"] = maplist (
		    string dn, modules [current_dn, "suseDefaultTemplate"]:[], {
			if (dn == template_dn) return new_dn;
			return dn;
		});
		modules_attrs_items [current_cn] =
		    create_attrs_items (current_cn);
		UI::ChangeWidget (`id(`table), `Items,
		    modules_attrs_items[current_cn]:[]);
		replace_templates_items ();
	    }
	    UI::SetFocus (`id(`table));
	}
	if (result == `next)
	{
	    Ldap::current_module_dn = current_dn;
	    // save the edited values to global map...
	    Ldap::CommitConfigModules (modules);
	    // commit templates here!
	    Ldap::CommitTemplates (templates);
	    break;
	}
	if (result == `cancel && ReallyAbort () != `abort)
	    result = `not_next;
	if (result == `back || result == `cancel)
	    break;
    }

    return (symbol) result;
}

}//EOF

ACC SHELL 2018