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