ACC SHELL
/**
* 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