#include "ldap_config.hpp" #include "ldap_config_mgr.hpp" #include "ldap_mapper_serialize.hpp" #include "utils.hpp" #include #include #include #include #include #include #include #include #include #include #include // Register class version // From cereal documentation; // "This macro should be placed at global scope" CEREAL_CLASS_VERSION(phosphor::ldap::Config, CLASS_VERSION); namespace phosphor { namespace ldap { constexpr auto nslcdService = "nslcd.service"; constexpr auto ldapScheme = "ldap"; constexpr auto ldapsScheme = "ldaps"; constexpr auto certObjPath = "/xyz/openbmc_project/certs/client/ldap/1"; constexpr auto certRootPath = "/xyz/openbmc_project/certs/client/ldap"; constexpr auto authObjPath = "/xyz/openbmc_project/certs/authority/truststore"; constexpr auto certIface = "xyz.openbmc_project.Certs.Certificate"; constexpr auto certProperty = "CertificateString"; using namespace phosphor::logging; using namespace sdbusplus::xyz::openbmc_project::Common::Error; namespace fs = std::filesystem; using Argument = xyz::openbmc_project::Common::InvalidArgument; using NotAllowedArgument = xyz::openbmc_project::Common::NotAllowed; using PrivilegeMappingExists = sdbusplus::xyz::openbmc_project::User::Common:: Error::PrivilegeMappingExists; using Line = std::string; using Key = std::string; using Val = std::string; using ConfigInfo = std::map; Config::Config(sdbusplus::bus_t& bus, const char* path, const char* filePath, const char* caCertFile, const char* certFile, bool secureLDAP, std::string ldapServerURI, std::string ldapBindDN, std::string ldapBaseDN, std::string&& ldapBindDNPassword, ConfigIface::SearchScope ldapSearchScope, ConfigIface::Type ldapType, bool ldapServiceEnabled, std::string userNameAttr, std::string groupNameAttr, ConfigMgr& parent) : Ifaces(bus, path, Ifaces::action::defer_emit), secureLDAP(secureLDAP), ldapBindPassword(std::move(ldapBindDNPassword)), tlsCacertFile(caCertFile), tlsCertFile(certFile), configFilePath(filePath), objectPath(path), bus(bus), parent(parent), certificateInstalledSignal( bus, sdbusplus::bus::match::rules::interfacesAdded(certRootPath), std::bind(std::mem_fn(&Config::certificateInstalled), this, std::placeholders::_1)), cacertificateInstalledSignal( bus, sdbusplus::bus::match::rules::interfacesAdded(authObjPath), std::bind(std::mem_fn(&Config::certificateInstalled), this, std::placeholders::_1)), certificateChangedSignal( bus, sdbusplus::bus::match::rules::propertiesChanged(certObjPath, certIface), std::bind(std::mem_fn(&Config::certificateChanged), this, std::placeholders::_1)) { ConfigIface::ldapServerURI(ldapServerURI); ConfigIface::ldapBindDN(ldapBindDN); ConfigIface::ldapBaseDN(ldapBaseDN); ConfigIface::ldapSearchScope(ldapSearchScope); ConfigIface::ldapType(ldapType); EnableIface::enabled(ldapServiceEnabled); ConfigIface::userNameAttribute(userNameAttr); ConfigIface::groupNameAttribute(groupNameAttr); // NOTE: Don't update the bindDN password under ConfigIface if (enabled()) { writeConfig(); } // save the config. configPersistPath = parent.dbusPersistentPath; configPersistPath += objectPath; // create the persistent directory fs::create_directories(configPersistPath); configPersistPath += "/config"; serialize(); // Emit deferred signal. this->emit_object_added(); parent.startOrStopService(nslcdService, enabled()); } Config::Config(sdbusplus::bus_t& bus, const char* path, const char* filePath, const char* caCertFile, const char* certFile, ConfigIface::Type ldapType, ConfigMgr& parent) : Ifaces(bus, path, Ifaces::action::defer_emit), secureLDAP(false), tlsCacertFile(caCertFile), tlsCertFile(certFile), configFilePath(filePath), objectPath(path), bus(bus), parent(parent), certificateInstalledSignal( bus, sdbusplus::bus::match::rules::interfacesAdded(certRootPath), std::bind(std::mem_fn(&Config::certificateInstalled), this, std::placeholders::_1)), cacertificateInstalledSignal( bus, sdbusplus::bus::match::rules::interfacesAdded(authObjPath), std::bind(std::mem_fn(&Config::certificateInstalled), this, std::placeholders::_1)), certificateChangedSignal( bus, sdbusplus::bus::match::rules::propertiesChanged(certObjPath, certIface), std::bind(std::mem_fn(&Config::certificateChanged), this, std::placeholders::_1)) { ConfigIface::ldapType(ldapType); configPersistPath = parent.dbusPersistentPath; configPersistPath += objectPath; // create the persistent directory fs::create_directories(configPersistPath); configPersistPath += "/config"; } void Config::certificateInstalled(sdbusplus::message_t& /*msg*/) { try { if (enabled()) { writeConfig(); } parent.startOrStopService(nslcdService, enabled()); } catch (const InternalFailure& e) { throw; } catch (const std::exception& e) { lg2::error("Exception: {ERR}", "ERR", e); elog(); } } void Config::certificateChanged(sdbusplus::message_t& msg) { std::string objectName; std::map> msgData; msg.read(objectName, msgData); auto valPropMap = msgData.find(certProperty); { if (valPropMap != msgData.end()) { try { if (enabled()) { writeConfig(); } parent.startOrStopService(nslcdService, enabled()); } catch (const InternalFailure& e) { throw; } catch (const std::exception& e) { lg2::error("Exception: {ERR}", "ERR", e); elog(); } } } } void Config::writeConfig() { std::stringstream confData; auto isPwdTobeWritten = false; std::string userNameAttr; confData << "uid root\n"; confData << "gid root\n\n"; confData << "ldap_version 3\n\n"; confData << "timelimit 30\n"; confData << "bind_timelimit 30\n"; confData << "pagesize 1000\n"; confData << "referrals off\n\n"; confData << "uri " << ldapServerURI() << "\n\n"; confData << "base " << ldapBaseDN() << "\n\n"; confData << "binddn " << ldapBindDN() << "\n"; if (!ldapBindPassword.empty()) { confData << "bindpw " << ldapBindPassword << "\n"; isPwdTobeWritten = true; } confData << "\n"; switch (ldapSearchScope()) { case ConfigIface::SearchScope::sub: confData << "scope sub\n\n"; break; case ConfigIface::SearchScope::one: confData << "scope one\n\n"; break; case ConfigIface::SearchScope::base: confData << "scope base\n\n"; break; } confData << "base passwd " << ldapBaseDN() << "\n"; confData << "base shadow " << ldapBaseDN() << "\n\n"; if (secureLDAP == true) { confData << "ssl on\n"; confData << "tls_reqcert hard\n"; if (fs::is_directory(tlsCacertFile.c_str())) { confData << "tls_cacertdir " << tlsCacertFile.c_str() << "\n"; } else { confData << "tls_cacertfile " << tlsCacertFile.c_str() << "\n"; } if (fs::exists(tlsCertFile.c_str())) { confData << "tls_cert " << tlsCertFile.c_str() << "\n"; confData << "tls_key " << tlsCertFile.c_str() << "\n"; } } else { confData << "ssl off\n"; } confData << "\n"; if (ldapType() == ConfigIface::Type::ActiveDirectory) { if (ConfigIface::userNameAttribute().empty()) { ConfigIface::userNameAttribute("sAMAccountName"); } if (ConfigIface::groupNameAttribute().empty()) { ConfigIface::groupNameAttribute("primaryGroupID"); } confData << "filter passwd (&(objectClass=user)(objectClass=person)" "(!(objectClass=computer)))\n"; confData << "filter group (|(objectclass=group)(objectclass=groupofnames) " "(objectclass=groupofuniquenames))\n"; confData << "map passwd uid " << ConfigIface::userNameAttribute() << "\n"; confData << "map passwd uidNumber " "objectSid:S-1-5-21-3623811015-3361044348-30300820\n"; confData << "map passwd gidNumber " << ConfigIface::groupNameAttribute() << "\n"; confData << "map passwd homeDirectory \"/home/$sAMAccountName\"\n"; confData << "map passwd gecos displayName\n"; confData << "map passwd loginShell \"/bin/sh\"\n"; confData << "map group gidNumber " "objectSid:S-1-5-21-3623811015-3361044348-30300820\n"; confData << "map group cn " << ConfigIface::userNameAttribute() << "\n"; confData << "nss_initgroups_ignoreusers ALLLOCAL\n"; } else if (ldapType() == ConfigIface::Type::OpenLdap) { if (ConfigIface::userNameAttribute().empty()) { ConfigIface::userNameAttribute("cn"); } if (ConfigIface::groupNameAttribute().empty()) { ConfigIface::groupNameAttribute("gidNumber"); } confData << "filter passwd (objectclass=*)\n"; confData << "map passwd gecos displayName\n"; confData << "filter group (objectclass=posixGroup)\n"; confData << "map passwd uid " << ConfigIface::userNameAttribute() << "\n"; confData << "map passwd gidNumber " << ConfigIface::groupNameAttribute() << "\n"; confData << "map passwd loginShell \"/bin/sh\"\n"; confData << "nss_initgroups_ignoreusers ALLLOCAL\n"; } try { std::fstream stream(configFilePath.c_str(), std::fstream::out); // remove the read permission from others if password is being written. // nslcd forces this behaviour. auto permission = fs::perms::owner_read | fs::perms::owner_write | fs::perms::group_read; if (isPwdTobeWritten) { fs::permissions(configFilePath, permission); } else { fs::permissions(configFilePath, permission | fs::perms::others_read); } stream << confData.str(); stream.flush(); stream.close(); } catch (const std::exception& e) { lg2::error("Exception: {ERR}", "ERR", e); elog(); } return; } std::string Config::ldapBindDNPassword(std::string value) { // Don't update the D-bus object, this is just to // facilitate if user wants to change the bind dn password // once d-bus object gets created. ldapBindPassword = value; try { if (enabled()) { writeConfig(); parent.startOrStopService(nslcdService, enabled()); } serialize(); } catch (const InternalFailure& e) { throw; } catch (const std::exception& e) { lg2::error("Exception: {ERR}", "ERR", e); elog(); } return value; } std::string Config::ldapServerURI(std::string value) { std::string val; try { if (value == ldapServerURI()) { return value; } if (isValidLDAPURI(value, ldapsScheme)) { secureLDAP = true; } else if (isValidLDAPURI(value, ldapScheme)) { secureLDAP = false; } else { lg2::error("Bad LDAP Server URI {URI}", "URI", value); elog(Argument::ARGUMENT_NAME("ldapServerURI"), Argument::ARGUMENT_VALUE(value.c_str())); } if (secureLDAP && !fs::exists(tlsCacertFile.c_str())) { lg2::error("LDAP server CA certificate not found at {PATH}", "PATH", tlsCacertFile); elog(); } val = ConfigIface::ldapServerURI(value); if (enabled()) { writeConfig(); parent.startOrStopService(nslcdService, enabled()); } // save the object. serialize(); } catch (const InternalFailure& e) { throw; } catch (const InvalidArgument& e) { throw; } catch (const NoCACertificate& e) { throw; } catch (const std::exception& e) { lg2::error("Exception: {ERR}", "ERR", e); elog(); } return val; } std::string Config::ldapBindDN(std::string value) { std::string val; try { if (value == ldapBindDN()) { return value; } if (value.empty()) { lg2::error("'{BINDDN}' is not a valid LDAP BindDN", "BINDDN", value); elog(Argument::ARGUMENT_NAME("ldapBindDN"), Argument::ARGUMENT_VALUE(value.c_str())); } val = ConfigIface::ldapBindDN(value); if (enabled()) { writeConfig(); parent.startOrStopService(nslcdService, enabled()); } // save the object. serialize(); } catch (const InternalFailure& e) { throw; } catch (const InvalidArgument& e) { throw; } catch (const std::exception& e) { lg2::error("Exception: {ERR}", "ERR", e); elog(); } return val; } std::string Config::ldapBaseDN(std::string value) { std::string val; try { if (value == ldapBaseDN()) { return value; } if (value.empty()) { lg2::error("'{BASEDN}' is not a valid LDAP BaseDN", "BASEDN", value); elog(Argument::ARGUMENT_NAME("ldapBaseDN"), Argument::ARGUMENT_VALUE(value.c_str())); } val = ConfigIface::ldapBaseDN(value); if (enabled()) { writeConfig(); parent.startOrStopService(nslcdService, enabled()); } // save the object. serialize(); } catch (const InternalFailure& e) { throw; } catch (const InvalidArgument& e) { throw; } catch (const std::exception& e) { lg2::error("Exception: {ERR}", "ERR", e); elog(); } return val; } ConfigIface::SearchScope Config::ldapSearchScope(ConfigIface::SearchScope value) { ConfigIface::SearchScope val; try { if (value == ldapSearchScope()) { return value; } val = ConfigIface::ldapSearchScope(value); if (enabled()) { writeConfig(); parent.startOrStopService(nslcdService, enabled()); } // save the object. serialize(); } catch (const InternalFailure& e) { throw; } catch (const std::exception& e) { lg2::error("Exception: {ERR}", "ERR", e); elog(); } return val; } ConfigIface::Type Config::ldapType(ConfigIface::Type /*value*/) { elog(NotAllowedArgument::REASON("ReadOnly Property")); return ldapType(); } bool Config::enabled(bool value) { if (value == enabled()) { return value; } // Let parent decide that can we enable this config. // It may happen that other config is already enabled, // Current implementation support only one config can // be active at a time. return parent.enableService(*this, value); } bool Config::enableService(bool value) { bool isEnable = false; try { isEnable = EnableIface::enabled(value); if (isEnable) { writeConfig(); } parent.startOrStopService(nslcdService, value); serialize(); } catch (const InternalFailure& e) { throw; } catch (const std::exception& e) { lg2::error("Exception: {ERR}", "ERR", e); elog(); } return isEnable; } std::string Config::userNameAttribute(std::string value) { std::string val; try { if (value == userNameAttribute()) { return value; } val = ConfigIface::userNameAttribute(value); if (enabled()) { writeConfig(); parent.startOrStopService(nslcdService, enabled()); } // save the object. serialize(); } catch (const InternalFailure& e) { throw; } catch (const std::exception& e) { lg2::error("Exception: {ERR}", "ERR", e); elog(); } return val; } std::string Config::groupNameAttribute(std::string value) { std::string val; try { if (value == groupNameAttribute()) { return value; } val = ConfigIface::groupNameAttribute(value); if (enabled()) { writeConfig(); parent.startOrStopService(nslcdService, enabled()); } // save the object. serialize(); } catch (const InternalFailure& e) { throw; } catch (const std::exception& e) { lg2::error("Exception: {ERR}", "ERR", e); elog(); } return val; } template void Config::save(Archive& archive, const std::uint32_t /*version*/) const { archive(this->enabled()); archive(ldapServerURI()); archive(ldapBindDN()); archive(ldapBaseDN()); archive(ldapSearchScope()); archive(ldapBindPassword); archive(userNameAttribute()); archive(groupNameAttribute()); } template void Config::load(Archive& archive, const std::uint32_t /*version*/) { bool bVal; archive(bVal); EnableIface::enabled(bVal); std::string str; archive(str); ConfigIface::ldapServerURI(str); archive(str); ConfigIface::ldapBindDN(str); archive(str); ConfigIface::ldapBaseDN(str); ConfigIface::SearchScope scope; archive(scope); ConfigIface::ldapSearchScope(scope); archive(str); ldapBindPassword = str; archive(str); ConfigIface::userNameAttribute(str); archive(str); ConfigIface::groupNameAttribute(str); } void Config::serialize() { if (!fs::exists(configPersistPath.c_str())) { std::ofstream os(configPersistPath.string(), std::ios::binary | std::ios::out); auto permission = fs::perms::owner_read | fs::perms::owner_write | fs::perms::group_read; fs::permissions(configPersistPath, permission); cereal::BinaryOutputArchive oarchive(os); oarchive(*this); } else { std::ofstream os(configPersistPath.string(), std::ios::binary | std::ios::out); cereal::BinaryOutputArchive oarchive(os); oarchive(*this); } return; } bool Config::deserialize() { try { if (fs::exists(configPersistPath)) { std::ifstream is(configPersistPath.c_str(), std::ios::in | std::ios::binary); cereal::BinaryInputArchive iarchive(is); iarchive(*this); if (isValidLDAPURI(ldapServerURI(), ldapScheme)) { secureLDAP = false; } else if (isValidLDAPURI(ldapServerURI(), ldapsScheme)) { secureLDAP = true; } return true; } return false; } catch (const cereal::Exception& e) { lg2::error("Exception: {ERR}", "ERR", e); std::error_code ec; fs::remove(configPersistPath, ec); return false; } catch (const fs::filesystem_error& e) { return false; } } ObjectPath Config::create(std::string groupName, std::string privilege) { checkPrivilegeMapper(groupName); checkPrivilegeLevel(privilege); entryId++; // Object path for the LDAP group privilege mapper entry fs::path mapperObjectPath = objectPath; mapperObjectPath /= "role_map"; mapperObjectPath /= std::to_string(entryId); fs::path persistPath = parent.dbusPersistentPath; persistPath += mapperObjectPath; // Create mapping for LDAP privilege mapper entry auto entry = std::make_unique( bus, mapperObjectPath.string().c_str(), persistPath.string().c_str(), groupName, privilege, *this); phosphor::ldap::serialize(*entry, std::move(persistPath)); PrivilegeMapperList.emplace(entryId, std::move(entry)); return mapperObjectPath.string(); } void Config::deletePrivilegeMapper(Id id) { fs::path mapperObjectPath = objectPath; mapperObjectPath /= "role_map"; mapperObjectPath /= std::to_string(id); fs::path persistPath = parent.dbusPersistentPath; persistPath += std::move(mapperObjectPath); // Delete the persistent representation of the privilege mapper. fs::remove(std::move(persistPath)); PrivilegeMapperList.erase(id); } void Config::checkPrivilegeMapper(const std::string& groupName) { if (groupName.empty()) { lg2::error("Group name is empty"); elog(Argument::ARGUMENT_NAME("Group name"), Argument::ARGUMENT_VALUE("Null")); } for (const auto& val : PrivilegeMapperList) { if (val.second.get()->groupName() == groupName) { lg2::error("Group name '{GROUPNAME}' already exists", "GROUPNAME", groupName); elog(); } } } void Config::checkPrivilegeLevel(const std::string& privilege) { if (privilege.empty()) { lg2::error("Privilege level is empty"); elog(Argument::ARGUMENT_NAME("Privilege level"), Argument::ARGUMENT_VALUE("Null")); } if (std::find(privMgr.begin(), privMgr.end(), privilege) == privMgr.end()) { lg2::error("Invalid privilege '{PRIVILEGE}'", "PRIVILEGE", privilege); elog(Argument::ARGUMENT_NAME("Privilege"), Argument::ARGUMENT_VALUE(privilege.c_str())); } } void Config::restoreRoleMapping() { namespace fs = std::filesystem; fs::path dir = parent.dbusPersistentPath; dir += objectPath; dir /= "role_map"; if (!fs::exists(dir) || fs::is_empty(dir)) { return; } for (auto& file : fs::directory_iterator(dir)) { std::string id = file.path().filename().c_str(); size_t idNum = std::stol(id); auto entryPath = objectPath + '/' + "role_map" + '/' + id; auto persistPath = parent.dbusPersistentPath + entryPath; auto entry = std::make_unique( bus, entryPath.c_str(), persistPath.c_str(), *this); if (phosphor::ldap::deserialize(file.path(), *entry)) { entry->Interfaces::emit_object_added(); PrivilegeMapperList.emplace(idNum, std::move(entry)); if (idNum > entryId) { entryId = idNum; } } } } } // namespace ldap } // namespace phosphor