1 #include "ldap_config_mgr.hpp"
2 #include "ldap_config.hpp"
3 #include "utils.hpp"
4 
5 #include <cereal/types/string.hpp>
6 #include <cereal/types/vector.hpp>
7 #include <cereal/archives/binary.hpp>
8 #include "ldap_mapper_serialize.hpp"
9 
10 #include <xyz/openbmc_project/Common/error.hpp>
11 #include <xyz/openbmc_project/User/Common/error.hpp>
12 #include <filesystem>
13 #include <fstream>
14 #include <sstream>
15 
16 // Register class version
17 // From cereal documentation;
18 // "This macro should be placed at global scope"
19 CEREAL_CLASS_VERSION(phosphor::ldap::Config, CLASS_VERSION);
20 
21 namespace phosphor
22 {
23 namespace ldap
24 {
25 
26 constexpr auto nslcdService = "nslcd.service";
27 constexpr auto nscdService = "nscd.service";
28 constexpr auto LDAPscheme = "ldap";
29 constexpr auto LDAPSscheme = "ldaps";
30 
31 using namespace phosphor::logging;
32 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
33 namespace fs = std::filesystem;
34 
35 using Argument = xyz::openbmc_project::Common::InvalidArgument;
36 using NotAllowed = sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
37 using NotAllowedArgument = xyz::openbmc_project::Common::NotAllowed;
38 using PrivilegeMappingExists = sdbusplus::xyz::openbmc_project::User::Common::
39     Error::PrivilegeMappingExists;
40 
41 using Line = std::string;
42 using Key = std::string;
43 using Val = std::string;
44 using ConfigInfo = std::map<Key, Val>;
45 
46 Config::Config(sdbusplus::bus::bus& bus, const char* path, const char* filePath,
47                const char* caCertFile, bool secureLDAP,
48                std::string lDAPServerURI, std::string lDAPBindDN,
49                std::string lDAPBaseDN, std::string&& lDAPBindDNPassword,
50                ConfigIface::SearchScope lDAPSearchScope,
51                ConfigIface::Type lDAPType, bool lDAPServiceEnabled,
52                std::string userNameAttr, std::string groupNameAttr,
53                ConfigMgr& parent) :
54     Ifaces(bus, path, true),
55     secureLDAP(secureLDAP), lDAPBindPassword(std::move(lDAPBindDNPassword)),
56     tlsCacertFile(caCertFile), configFilePath(filePath), objectPath(path),
57     bus(bus), parent(parent)
58 {
59     ConfigIface::lDAPServerURI(lDAPServerURI);
60     ConfigIface::lDAPBindDN(lDAPBindDN);
61     ConfigIface::lDAPBaseDN(lDAPBaseDN);
62     ConfigIface::lDAPSearchScope(lDAPSearchScope);
63     ConfigIface::lDAPType(lDAPType);
64     EnableIface::enabled(lDAPServiceEnabled);
65     ConfigIface::userNameAttribute(userNameAttr);
66     ConfigIface::groupNameAttribute(groupNameAttr);
67     // NOTE: Don't update the bindDN password under ConfigIface
68     if (enabled())
69     {
70         writeConfig();
71     }
72     // save the config.
73     configPersistPath = parent.dbusPersistentPath;
74     configPersistPath += objectPath;
75 
76     // create the persistent directory
77     fs::create_directories(configPersistPath);
78 
79     configPersistPath += "/config";
80 
81     std::ofstream os(configPersistPath, std::ios::binary | std::ios::out);
82     // remove the read permission from others
83     auto permission =
84         fs::perms::owner_read | fs::perms::owner_write | fs::perms::group_read;
85     fs::permissions(configPersistPath, permission);
86 
87     serialize();
88 
89     // Emit deferred signal.
90     this->emit_object_added();
91     parent.startOrStopService(nslcdService, enabled());
92 }
93 
94 Config::Config(sdbusplus::bus::bus& bus, const char* path, const char* filePath,
95                const char* caCertFile, ConfigIface::Type lDAPType,
96                ConfigMgr& parent) :
97     Ifaces(bus, path, true),
98     tlsCacertFile(caCertFile), configFilePath(filePath), objectPath(path),
99     bus(bus), parent(parent)
100 {
101     ConfigIface::lDAPType(lDAPType);
102 
103     configPersistPath = parent.dbusPersistentPath;
104     configPersistPath += objectPath;
105 
106     // create the persistent directory
107     fs::create_directories(configPersistPath);
108 
109     configPersistPath += "/config";
110 
111     std::ofstream os(configPersistPath, std::ios::binary | std::ios::out);
112     // remove the read permission from others
113     auto permission =
114         fs::perms::owner_read | fs::perms::owner_write | fs::perms::group_read;
115     fs::permissions(configPersistPath, permission);
116 }
117 
118 void Config::writeConfig()
119 {
120     std::stringstream confData;
121     auto isPwdTobeWritten = false;
122     std::string userNameAttr;
123 
124     confData << "uid root\n";
125     confData << "gid root\n\n";
126     confData << "ldap_version 3\n\n";
127     confData << "timelimit 30\n";
128     confData << "bind_timelimit 30\n";
129     confData << "pagesize 1000\n";
130     confData << "referrals off\n\n";
131     confData << "uri " << lDAPServerURI() << "\n\n";
132     confData << "base " << lDAPBaseDN() << "\n\n";
133     confData << "binddn " << lDAPBindDN() << "\n";
134     if (!lDAPBindPassword.empty())
135     {
136         confData << "bindpw " << lDAPBindPassword << "\n";
137         isPwdTobeWritten = true;
138     }
139     confData << "\n";
140     switch (lDAPSearchScope())
141     {
142         case ConfigIface::SearchScope::sub:
143             confData << "scope sub\n\n";
144             break;
145         case ConfigIface::SearchScope::one:
146             confData << "scope one\n\n";
147             break;
148         case ConfigIface::SearchScope::base:
149             confData << "scope base\n\n";
150             break;
151     }
152     confData << "base passwd " << lDAPBaseDN() << "\n";
153     confData << "base shadow " << lDAPBaseDN() << "\n\n";
154     if (secureLDAP == true)
155     {
156         confData << "ssl on\n";
157         confData << "tls_reqcert hard\n";
158         confData << "tls_cacertFile " << tlsCacertFile.c_str() << "\n";
159     }
160     else
161     {
162         confData << "ssl off\n";
163     }
164     confData << "\n";
165     if (lDAPType() == ConfigIface::Type::ActiveDirectory)
166     {
167         if (ConfigIface::userNameAttribute().empty())
168         {
169             ConfigIface::userNameAttribute("sAMAccountName");
170         }
171         if (ConfigIface::groupNameAttribute().empty())
172         {
173             ConfigIface::groupNameAttribute("primaryGroupID");
174         }
175         confData << "filter passwd (&(objectClass=user)(objectClass=person)"
176                     "(!(objectClass=computer)))\n";
177         confData
178             << "filter group (|(objectclass=group)(objectclass=groupofnames) "
179                "(objectclass=groupofuniquenames))\n";
180         confData << "map passwd uid              "
181                  << ConfigIface::userNameAttribute() << "\n";
182         confData << "map passwd uidNumber        "
183                     "objectSid:S-1-5-21-3623811015-3361044348-30300820\n";
184         confData << "map passwd gidNumber        "
185                  << ConfigIface::groupNameAttribute() << "\n";
186         confData << "map passwd homeDirectory    \"/home/$sAMAccountName\"\n";
187         confData << "map passwd gecos            displayName\n";
188         confData << "map passwd loginShell       \"/bin/bash\"\n";
189         confData << "map group gidNumber         "
190                     "objectSid:S-1-5-21-3623811015-3361044348-30300820\n";
191         confData << "map group cn                "
192                  << ConfigIface::userNameAttribute() << "\n";
193     }
194     else if (lDAPType() == ConfigIface::Type::OpenLdap)
195     {
196         if (ConfigIface::userNameAttribute().empty())
197         {
198             ConfigIface::userNameAttribute("cn");
199         }
200         if (ConfigIface::groupNameAttribute().empty())
201         {
202             ConfigIface::groupNameAttribute("gidNumber");
203         }
204         confData << "filter passwd (objectclass=*)\n";
205         confData << "map passwd gecos displayName\n";
206         confData << "filter group (objectclass=posixGroup)\n";
207         confData << "map passwd uid              "
208                  << ConfigIface::userNameAttribute() << "\n";
209         confData << "map passwd gidNumber        "
210                  << ConfigIface::groupNameAttribute() << "\n";
211     }
212     try
213     {
214         std::fstream stream(configFilePath.c_str(), std::fstream::out);
215         // remove the read permission from others if password is being written.
216         // nslcd forces this behaviour.
217         auto permission = fs::perms::owner_read | fs::perms::owner_write |
218                           fs::perms::group_read;
219         if (isPwdTobeWritten)
220         {
221             fs::permissions(configFilePath, permission);
222         }
223         else
224         {
225             fs::permissions(configFilePath,
226                             permission | fs::perms::others_read);
227         }
228 
229         stream << confData.str();
230         stream.flush();
231         stream.close();
232     }
233     catch (const std::exception& e)
234     {
235         log<level::ERR>(e.what());
236         elog<InternalFailure>();
237     }
238     return;
239 }
240 
241 std::string Config::lDAPBindDNPassword(std::string value)
242 {
243     // Don't update the D-bus object, this is just to
244     // facilitate if user wants to change the bind dn password
245     // once d-bus object gets created.
246     lDAPBindPassword = value;
247     try
248     {
249         if (enabled())
250         {
251             writeConfig();
252             parent.startOrStopService(nslcdService, enabled());
253         }
254         serialize();
255     }
256     catch (const InternalFailure& e)
257     {
258         throw;
259     }
260     catch (const std::exception& e)
261     {
262         log<level::ERR>(e.what());
263         elog<InternalFailure>();
264     }
265     return value;
266 }
267 
268 std::string Config::lDAPServerURI(std::string value)
269 {
270     std::string val;
271     try
272     {
273         if (value == lDAPServerURI())
274         {
275             return value;
276         }
277         if (isValidLDAPURI(value, LDAPSscheme))
278         {
279             secureLDAP = true;
280         }
281         else if (isValidLDAPURI(value, LDAPscheme))
282         {
283             secureLDAP = false;
284         }
285         else
286         {
287             log<level::ERR>("bad LDAP Server URI",
288                             entry("LDAPSERVERURI=%s", value.c_str()));
289             elog<InvalidArgument>(Argument::ARGUMENT_NAME("lDAPServerURI"),
290                                   Argument::ARGUMENT_VALUE(value.c_str()));
291         }
292 
293         if (secureLDAP && !fs::exists(tlsCacertFile.c_str()))
294         {
295             log<level::ERR>("LDAP server's CA certificate not provided",
296                             entry("TLSCACERTFILE=%s", tlsCacertFile.c_str()));
297             elog<NoCACertificate>();
298         }
299         val = ConfigIface::lDAPServerURI(value);
300         if (enabled())
301         {
302             writeConfig();
303             parent.startOrStopService(nslcdService, enabled());
304         }
305         // save the object.
306         serialize();
307     }
308     catch (const InternalFailure& e)
309     {
310         throw;
311     }
312     catch (const InvalidArgument& e)
313     {
314         throw;
315     }
316     catch (const NoCACertificate& e)
317     {
318         throw;
319     }
320     catch (const std::exception& e)
321     {
322         log<level::ERR>(e.what());
323         elog<InternalFailure>();
324     }
325     return val;
326 }
327 
328 std::string Config::lDAPBindDN(std::string value)
329 {
330     std::string val;
331     try
332     {
333         if (value == lDAPBindDN())
334         {
335             return value;
336         }
337 
338         if (value.empty())
339         {
340             log<level::ERR>("Not a valid LDAP BINDDN",
341                             entry("LDAPBINDDN=%s", value.c_str()));
342             elog<InvalidArgument>(Argument::ARGUMENT_NAME("lDAPBindDN"),
343                                   Argument::ARGUMENT_VALUE(value.c_str()));
344         }
345 
346         val = ConfigIface::lDAPBindDN(value);
347         if (enabled())
348         {
349             writeConfig();
350             parent.startOrStopService(nslcdService, enabled());
351         }
352         // save the object.
353         serialize();
354     }
355     catch (const InternalFailure& e)
356     {
357         throw;
358     }
359     catch (const InvalidArgument& e)
360     {
361         throw;
362     }
363     catch (const std::exception& e)
364     {
365         log<level::ERR>(e.what());
366         elog<InternalFailure>();
367     }
368     return val;
369 }
370 
371 std::string Config::lDAPBaseDN(std::string value)
372 {
373     std::string val;
374     try
375     {
376         if (value == lDAPBaseDN())
377         {
378             return value;
379         }
380 
381         if (value.empty())
382         {
383             log<level::ERR>("Not a valid LDAP BASEDN",
384                             entry("BASEDN=%s", value.c_str()));
385             elog<InvalidArgument>(Argument::ARGUMENT_NAME("lDAPBaseDN"),
386                                   Argument::ARGUMENT_VALUE(value.c_str()));
387         }
388 
389         val = ConfigIface::lDAPBaseDN(value);
390         if (enabled())
391         {
392             writeConfig();
393             parent.startOrStopService(nslcdService, enabled());
394         }
395         // save the object.
396         serialize();
397     }
398     catch (const InternalFailure& e)
399     {
400         throw;
401     }
402     catch (const InvalidArgument& e)
403     {
404         throw;
405     }
406     catch (const std::exception& e)
407     {
408         log<level::ERR>(e.what());
409         elog<InternalFailure>();
410     }
411     return val;
412 }
413 
414 ConfigIface::SearchScope Config::lDAPSearchScope(ConfigIface::SearchScope value)
415 {
416     ConfigIface::SearchScope val;
417     try
418     {
419         if (value == lDAPSearchScope())
420         {
421             return value;
422         }
423 
424         val = ConfigIface::lDAPSearchScope(value);
425         if (enabled())
426         {
427             writeConfig();
428 
429             parent.startOrStopService(nslcdService, enabled());
430         }
431         // save the object.
432         serialize();
433     }
434     catch (const InternalFailure& e)
435     {
436         throw;
437     }
438     catch (const std::exception& e)
439     {
440         log<level::ERR>(e.what());
441         elog<InternalFailure>();
442     }
443     return val;
444 }
445 
446 ConfigIface::Type Config::lDAPType(ConfigIface::Type value)
447 {
448     elog<NotAllowed>(NotAllowedArgument::REASON("ReadOnly Property"));
449     return lDAPType();
450 }
451 
452 bool Config::enabled(bool value)
453 {
454     if (value == enabled())
455     {
456         return value;
457     }
458     // Let parent decide that can we enable this config.
459     // It may happen that other config is already enabled,
460     // Current implementation support only one config can
461     // be active at a time.
462     return parent.enableService(*this, value);
463 }
464 
465 bool Config::enableService(bool value)
466 {
467     bool isEnable = false;
468     try
469     {
470         isEnable = EnableIface::enabled(value);
471         if (isEnable)
472         {
473             writeConfig();
474         }
475         parent.startOrStopService(nslcdService, value);
476         serialize();
477     }
478     catch (const InternalFailure& e)
479     {
480         throw;
481     }
482     catch (const std::exception& e)
483     {
484         log<level::ERR>(e.what());
485         elog<InternalFailure>();
486     }
487     return isEnable;
488 }
489 
490 std::string Config::userNameAttribute(std::string value)
491 {
492     std::string val;
493     try
494     {
495         if (value == userNameAttribute())
496         {
497             return value;
498         }
499 
500         val = ConfigIface::userNameAttribute(value);
501         if (enabled())
502         {
503             writeConfig();
504 
505             parent.startOrStopService(nslcdService, enabled());
506         }
507         // save the object.
508         serialize();
509     }
510     catch (const InternalFailure& e)
511     {
512         throw;
513     }
514     catch (const std::exception& e)
515     {
516         log<level::ERR>(e.what());
517         elog<InternalFailure>();
518     }
519     return val;
520 }
521 
522 std::string Config::groupNameAttribute(std::string value)
523 {
524     std::string val;
525     try
526     {
527         if (value == groupNameAttribute())
528         {
529             return value;
530         }
531 
532         val = ConfigIface::groupNameAttribute(value);
533         if (enabled())
534         {
535             writeConfig();
536 
537             parent.startOrStopService(nslcdService, enabled());
538         }
539         // save the object.
540         serialize();
541     }
542     catch (const InternalFailure& e)
543     {
544         throw;
545     }
546     catch (const std::exception& e)
547     {
548         log<level::ERR>(e.what());
549         elog<InternalFailure>();
550     }
551     return val;
552 }
553 
554 template <class Archive>
555 void Config::save(Archive& archive, const std::uint32_t version) const
556 {
557     archive(this->enabled());
558     archive(lDAPServerURI());
559     archive(lDAPBindDN());
560     archive(lDAPBaseDN());
561     archive(lDAPSearchScope());
562     archive(lDAPBindPassword);
563     archive(userNameAttribute());
564     archive(groupNameAttribute());
565 }
566 
567 template <class Archive>
568 void Config::load(Archive& archive, const std::uint32_t version)
569 {
570 
571     bool bVal;
572     archive(bVal);
573     EnableIface::enabled(bVal);
574 
575     std::string str;
576     archive(str);
577     ConfigIface::lDAPServerURI(str);
578 
579     archive(str);
580     ConfigIface::lDAPBindDN(str);
581 
582     archive(str);
583     ConfigIface::lDAPBaseDN(str);
584 
585     ConfigIface::SearchScope scope;
586     archive(scope);
587     ConfigIface::lDAPSearchScope(scope);
588 
589     archive(str);
590     lDAPBindPassword = str;
591 
592     archive(str);
593     ConfigIface::userNameAttribute(str);
594 
595     archive(str);
596     ConfigIface::groupNameAttribute(str);
597 }
598 
599 void Config::serialize()
600 {
601     std::ofstream os(configPersistPath.string(),
602                      std::ios::binary | std::ios::out);
603     cereal::BinaryOutputArchive oarchive(os);
604     oarchive(*this);
605     return;
606 }
607 
608 bool Config::deserialize()
609 {
610     try
611     {
612         if (fs::exists(configPersistPath))
613         {
614             std::ifstream is(configPersistPath.c_str(),
615                              std::ios::in | std::ios::binary);
616             cereal::BinaryInputArchive iarchive(is);
617             iarchive(*this);
618             return true;
619         }
620         return false;
621     }
622     catch (cereal::Exception& e)
623     {
624         log<level::ERR>(e.what());
625         std::error_code ec;
626         fs::remove(configPersistPath, ec);
627         return false;
628     }
629     catch (const fs::filesystem_error& e)
630     {
631         return false;
632     }
633 }
634 
635 ObjectPath Config::create(std::string groupName, std::string privilege)
636 {
637     checkPrivilegeMapper(groupName);
638     checkPrivilegeLevel(privilege);
639 
640     entryId++;
641 
642     // Object path for the LDAP group privilege mapper entry
643     fs::path mapperObjectPath = objectPath;
644     mapperObjectPath /= "role_map";
645     mapperObjectPath /= std::to_string(entryId);
646 
647     fs::path persistPath = parent.dbusPersistentPath;
648     persistPath += mapperObjectPath;
649 
650     // Create mapping for LDAP privilege mapper entry
651     auto entry = std::make_unique<LDAPMapperEntry>(
652         bus, mapperObjectPath.string().c_str(), persistPath.string().c_str(),
653         groupName, privilege, *this);
654 
655     phosphor::ldap::serialize(*entry, std::move(persistPath));
656 
657     PrivilegeMapperList.emplace(entryId, std::move(entry));
658     return mapperObjectPath.string();
659 }
660 
661 void Config::deletePrivilegeMapper(Id id)
662 {
663     fs::path mapperObjectPath = objectPath;
664     mapperObjectPath /= "role_map";
665     mapperObjectPath /= std::to_string(id);
666 
667     fs::path persistPath = parent.dbusPersistentPath;
668     persistPath += std::move(mapperObjectPath);
669 
670     // Delete the persistent representation of the privilege mapper.
671     fs::remove(std::move(persistPath));
672 
673     PrivilegeMapperList.erase(id);
674 }
675 void Config::checkPrivilegeMapper(const std::string& groupName)
676 {
677     if (groupName.empty())
678     {
679         log<level::ERR>("Group name is empty");
680         elog<InvalidArgument>(Argument::ARGUMENT_NAME("Group name"),
681                               Argument::ARGUMENT_VALUE("Null"));
682     }
683 
684     for (const auto& val : PrivilegeMapperList)
685     {
686         if (val.second.get()->groupName() == groupName)
687         {
688             log<level::ERR>("Group name already exists");
689             elog<PrivilegeMappingExists>();
690         }
691     }
692 }
693 
694 void Config::checkPrivilegeLevel(const std::string& privilege)
695 {
696     if (privilege.empty())
697     {
698         log<level::ERR>("Privilege level is empty");
699         elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege level"),
700                               Argument::ARGUMENT_VALUE("Null"));
701     }
702 
703     if (std::find(privMgr.begin(), privMgr.end(), privilege) == privMgr.end())
704     {
705         log<level::ERR>("Invalid privilege");
706         elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege level"),
707                               Argument::ARGUMENT_VALUE(privilege.c_str()));
708     }
709 }
710 
711 void Config::restoreRoleMapping()
712 {
713     namespace fs = std::filesystem;
714     fs::path dir = parent.dbusPersistentPath;
715     dir += objectPath;
716     dir /= "role_map";
717 
718     if (!fs::exists(dir) || fs::is_empty(dir))
719     {
720         return;
721     }
722 
723     for (auto& file : fs::directory_iterator(dir))
724     {
725         std::string id = file.path().filename().c_str();
726         size_t idNum = std::stol(id);
727 
728         auto entryPath = objectPath + '/' + "role_map" + '/' + id;
729         auto persistPath = parent.dbusPersistentPath + entryPath;
730         auto entry = std::make_unique<LDAPMapperEntry>(
731             bus, entryPath.c_str(), persistPath.c_str(), *this);
732         if (phosphor::ldap::deserialize(file.path(), *entry))
733         {
734             entry->Interfaces::emit_object_added();
735             PrivilegeMapperList.emplace(idNum, std::move(entry));
736             if (idNum > entryId)
737             {
738                 entryId = idNum;
739             }
740         }
741     }
742 }
743 
744 } // namespace ldap
745 } // namespace phosphor
746