Loading plugin/ad/ad.cpp +73 −9 Changes for plugin/ad/ad.cpp: 73 added lines, 9 removed lines. Original line number Diff line number Diff line Loading @@ -549,6 +549,9 @@ bool ActiveDirectory::dirList(const std::string &domain, int kind, if (!d) { err = "unknown domain '" + domain + "'"; return false; } dsdb::Filter f; if (kind == DirComputer) { f = dsdb::Filter::equal("objectClass", "computer"); } else { f.type = dsdb::Filter::And; f.children.push_back(dsdb::Filter::equal("objectClass", kind == DirGroup ? "group" : "user")); Loading @@ -559,14 +562,18 @@ bool ActiveDirectory::dirList(const std::string &domain, int kind, notComp.children.push_back(dsdb::Filter::equal("objectClass", "computer")); f.children.push_back(notComp); } } dsdb::Dn base = d->meta.baseDn.child("CN", "Users"); /* computers can live under CN=Computers or CN=Users — search the whole * domain for them; users/groups stay under CN=Users */ dsdb::Dn base = (kind == DirComputer) ? d->meta.baseDn : d->meta.baseDn.child("CN", "Users"); out.clear(); d->dir->search(base, LDAP_SCOPE_SUBTREE, f, [&](const dsdb::Entry &e) -> bool { out.push_back(e); return true; }); std::cerr << "[AD] dirList domain=" << domain << " kind=" << (kind == DirGroup ? "group" : "user") const char *kn = kind == DirGroup ? "group" : (kind == DirComputer ? "computer" : "user"); std::cerr << "[AD] dirList domain=" << domain << " kind=" << kn << " base=" << base.str() << " -> " << out.size() << " objects" << std::endl; return true; } Loading Loading @@ -693,7 +700,7 @@ bool ActiveDirectory::dirModify(const std::string &domain, const uuid::uuid &gui } try { d->dir->modify(guid, merged, sec.get()); d->dir->modify(guid, merged, sec.get(), true); /* merge: keep other attrs */ } catch (std::exception &e2) { err = e2.what(); return false; Loading @@ -720,6 +727,55 @@ bool ActiveDirectory::dirRemove(const std::string &domain, const uuid::uuid &gui return true; } bool ActiveDirectory::dirSetWindowsLogin(const std::string &domain, const uuid::uuid &guid, bool enable, const std::string &clearPassword, std::string &err) { if (g_Replication && g_Replication->isSlave()) { err = "this node is a replication slave (read-only); make changes on the master"; return false; } std::lock_guard<std::mutex> lock(_Lock); Dom *d = domainByName(domain); if (!d) { err = "unknown domain '" + domain + "'"; return false; } auto e = d->dir->byGuid(guid); if (!e) { err = "object not found"; return false; } std::string sam = e->str("sAMAccountName"); if (sam.empty()) { err = "object has no sAMAccountName"; return false; } if (enable) { if (!e->objectSid.empty()) return true; /* already a Windows account */ if (clearPassword.empty()) { err = "a password is required to enable Windows login"; return false; } uint32_t rid = d->dir->allocateRid(); std::string salt = dsdb::kerberosSalt(dsdb::PrincipalKind::User, d->meta.realm, sam, d->meta.dnsName); dsdb::Secrets sec = dsdb::deriveSecrets(clearPassword, salt, 0); dsdb::AttrMap a; a["objectSid"] = {d->meta.sid.withRid(rid).binary()}; dsdb::setInt(a, "sAMAccountType", dsdb::SAM_USER_OBJECT); dsdb::setInt(a, "userAccountControl", dsdb::UAC_NORMAL_ACCOUNT | dsdb::UAC_DONT_EXPIRE_PASSWORD); dsdb::setInt(a, "primaryGroupID", dsdb::RID_DOMAIN_USERS); dsdb::setInt(a, "msDS-KeyVersionNumber", sec.kvno); dsdb::setInt(a, "pwdLastSet", sec.pwdLastSet); if (e->str("userPrincipalName").empty()) a["userPrincipalName"] = {sam + "@" + d->meta.dnsName}; try { d->dir->modify(guid, a, &sec, true); } catch (std::exception &ex) { err = ex.what(); return false; } std::cerr << "[AD] enabled Windows login for '" << sam << "' (RID " << rid << ")" << std::endl; } else { if (e->objectSid.empty()) return true; /* already SID-less */ dsdb::AttrMap a; /* empty value = delete attribute */ a["objectSid"] = {}; a["sAMAccountType"] = {}; a["userAccountControl"] = {}; a["primaryGroupID"] = {}; a["msDS-KeyVersionNumber"] = {}; try { d->dir->modify(guid, a, nullptr, true); } catch (std::exception &ex) { err = ex.what(); return false; } std::cerr << "[AD] disabled Windows login for '" << sam << "' (now SID-less)" << std::endl; } return true; } bool ActiveDirectory::dirAuthenticate(const std::string &domain, const std::string &sam, const std::string &pw, uuid::uuid &outGuid, std::string &err) { Loading Loading @@ -808,8 +864,8 @@ bool ActiveDirectory::dirAddMember(const std::string &domain, const uuid::uuid & ma["memberOf"] = memberOf; try { d->dir->modify(group, ga, nullptr); d->dir->modify(member, ma, nullptr); d->dir->modify(group, ga, nullptr, true); d->dir->modify(member, ma, nullptr, true); } catch (std::exception &e) { err = e.what(); return false; Loading Loading @@ -842,8 +898,8 @@ bool ActiveDirectory::dirRemoveMember(const std::string &domain, const uuid::uui ma["memberOf"] = memberOf; try { d->dir->modify(group, ga, nullptr); d->dir->modify(member, ma, nullptr); d->dir->modify(group, ga, nullptr, true); d->dir->modify(member, ma, nullptr, true); } catch (std::exception &e) { err = e.what(); return false; Loading Loading @@ -919,6 +975,14 @@ bool ActiveDirectory::dirImportNative(const std::string &domain, int &migrated, if (existing) { groupGuid = existing->objectGuid; groupDn = existing->dn.str(); /* repair a group whose attributes an earlier buggy modify wiped */ if (existing->str("sAMAccountName").empty()) { dsdb::AttrMap fix; fix["objectClass"] = {"top", "group"}; fix["sAMAccountName"] = {gname}; fix["cn"] = {gname}; fix["name"] = {gname}; try { d->dir->modify(groupGuid, fix, nullptr, true); } catch (...) {} } } else { dsdb::AttrMap a; a["objectClass"] = {"top", "group"}; Loading Loading @@ -953,14 +1017,14 @@ bool ActiveDirectory::dirImportNative(const std::string &domain, int &migrated, if (std::find(members.begin(), members.end(), mdn) == members.end()) { members.push_back(mdn); dsdb::AttrMap ga = grpEnt->attrs; ga["member"] = members; try { d->dir->modify(groupGuid, ga, nullptr); } catch (...) {} try { d->dir->modify(groupGuid, ga, nullptr, true); } catch (...) {} } } dsdb::Values mo = me->get("memberOf") ? *me->get("memberOf") : dsdb::Values{}; if (std::find(mo.begin(), mo.end(), groupDn) == mo.end()) { mo.push_back(groupDn); dsdb::AttrMap ua = me->attrs; ua["memberOf"] = mo; try { d->dir->modify(me->objectGuid, ua, nullptr); } catch (...) {} try { d->dir->modify(me->objectGuid, ua, nullptr, true); } catch (...) {} } } } Loading plugin/ad/ad.h +3 −0 Changes for plugin/ad/ad.h: 3 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -283,6 +283,9 @@ namespace fenrirds { bool dirImportNative(const std::string &domain, int &migrated, std::string &err, std::map<std::string, std::string> *groupMap = nullptr) override; bool dirSetWindowsLogin(const std::string &domain, const uuid::uuid &guid, bool enable, const std::string &clearPassword, std::string &err) override; bool dirList(const std::string &domain, int kind, std::vector<dsdb::Entry> &out, std::string &err) override; bool dirGet(const std::string &domain, const uuid::uuid &guid, Loading src/admin.cpp +39 −0 Changes for src/admin.cpp: 39 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -403,6 +403,45 @@ namespace fenrirds { json_object_put(jobj); } /* ---- JSON API: List Computers (directory objectClass=computer) ---- */ void apiListComputers(libhttppp::HttpRequest &curreq, const char *cdid) { std::string domain = domainNameFor(cdid); if (domain.empty() || !g_AdPlugin) throw AuthBackendError("listComputers: no directory for domain!"); std::vector<dsdb::Entry> ents; std::string err; if (!g_AdPlugin->dirList(domain, AdPlugin::DirComputer, ents, err)) throw AuthBackendError("listComputers: " + err); json_object *jobj = json_object_new_object(); json_object *jarr = json_object_new_array(); for (const auto &e : ents) { json_object *jc = json_object_new_object(); json_object_object_add(jc, "id", json_object_new_string(e.objectGuid.c_str())); json_object_object_add(jc, "name", json_object_new_string(e.str("sAMAccountName").c_str())); json_object_object_add(jc, "dnshostname", json_object_new_string(e.str("dNSHostName").c_str())); json_object_array_add(jarr, jc); } json_object_object_add(jobj, "computers", jarr); sendJson(curreq, jobj); json_object_put(jobj); } /* ---- JSON API: Remove Computer (enables a clean domain rejoin) ---- */ void apiRemoveComputer(libhttppp::HttpRequest &curreq, const char *cdid, const char *cid) { std::string domain = domainNameFor(cdid); if (domain.empty() || !g_AdPlugin) throw AuthBackendError("removeComputer: no directory for domain!"); uuid::uuid gid; gid.parse(cid); std::string err; if (!g_AdPlugin->dirRemove(domain, gid, err)) throw AuthBackendError("removeComputer: " + err); json_object *jobj = json_object_new_object(); json_object_object_add(jobj, "status", json_object_new_string("ok")); sendJson(curreq, jobj); json_object_put(jobj); } /* ---- JSON API: Edit User (directory object) ---- */ void apiEditUser(libhttppp::HttpRequest &curreq, const char *cdid, const char *cuid) { if (strcmp(cdid, "admin") == 0) { Loading src/dsdb/directory.cpp +12 −1 Changes for src/dsdb/directory.cpp: 12 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -284,7 +284,7 @@ uuid::uuid Directory::create(const Dn &dn, AttrMap attrs, const Secrets *secrets return guid; } void Directory::modify(const uuid::uuid &guid, const AttrMap &attrs, const Secrets *secrets) { void Directory::modify(const uuid::uuid &guid, const AttrMap &attrs, const Secrets *secrets, bool mergeAttrs) { std::lock_guard<std::mutex> lk(_Mtx); ensureFresh(); Dn dn; Loading @@ -292,7 +292,18 @@ void Directory::modify(const uuid::uuid &guid, const AttrMap &attrs, const Secre std::string sblob; if (!loadObject(_Backend, guid, dn, cur, sblob)) throw std::runtime_error("no such object"); if (mergeAttrs) { /* overlay: keep every attribute not named in `attrs`; an empty value * list deletes that attribute */ for (const auto &kv : attrs) { if (kv.second.empty()) cur.erase(kv.first); else cur[kv.first] = kv.second; } } else { cur = attrs; } cur["whenChanged"] = {generalizedTime(unixFromFileTime(fileTimeNow()))}; if (secrets) sblob = secrets->serialize(); Loading src/dsdb/directory.h +7 −1 Changes for src/dsdb/directory.h: 7 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -117,7 +117,13 @@ namespace fenrirds { * consistent. create allocates objectGUID (if empty) and the SID * for a security principal; returns the new objectGUID. */ uuid::uuid create(const Dn &dn, AttrMap attrs, const Secrets *secrets = nullptr); void modify(const uuid::uuid &guid, const AttrMap &attrs, const Secrets *secrets = nullptr); /* Replace the object's attributes with `attrs` (LDAP semantics). * With mergeAttrs=true, overlay `attrs` onto the existing ones * instead (partial update) — used for member/memberOf and single * field edits so unrelated attributes (sAMAccountName, objectSid…) * are preserved. */ void modify(const uuid::uuid &guid, const AttrMap &attrs, const Secrets *secrets = nullptr, bool mergeAttrs = false); void remove(const uuid::uuid &guid); /* next RID from the domain head's pool */ uint32_t allocateRid(); Loading Loading
plugin/ad/ad.cpp +73 −9 Changes for plugin/ad/ad.cpp: 73 added lines, 9 removed lines. Original line number Diff line number Diff line Loading @@ -549,6 +549,9 @@ bool ActiveDirectory::dirList(const std::string &domain, int kind, if (!d) { err = "unknown domain '" + domain + "'"; return false; } dsdb::Filter f; if (kind == DirComputer) { f = dsdb::Filter::equal("objectClass", "computer"); } else { f.type = dsdb::Filter::And; f.children.push_back(dsdb::Filter::equal("objectClass", kind == DirGroup ? "group" : "user")); Loading @@ -559,14 +562,18 @@ bool ActiveDirectory::dirList(const std::string &domain, int kind, notComp.children.push_back(dsdb::Filter::equal("objectClass", "computer")); f.children.push_back(notComp); } } dsdb::Dn base = d->meta.baseDn.child("CN", "Users"); /* computers can live under CN=Computers or CN=Users — search the whole * domain for them; users/groups stay under CN=Users */ dsdb::Dn base = (kind == DirComputer) ? d->meta.baseDn : d->meta.baseDn.child("CN", "Users"); out.clear(); d->dir->search(base, LDAP_SCOPE_SUBTREE, f, [&](const dsdb::Entry &e) -> bool { out.push_back(e); return true; }); std::cerr << "[AD] dirList domain=" << domain << " kind=" << (kind == DirGroup ? "group" : "user") const char *kn = kind == DirGroup ? "group" : (kind == DirComputer ? "computer" : "user"); std::cerr << "[AD] dirList domain=" << domain << " kind=" << kn << " base=" << base.str() << " -> " << out.size() << " objects" << std::endl; return true; } Loading Loading @@ -693,7 +700,7 @@ bool ActiveDirectory::dirModify(const std::string &domain, const uuid::uuid &gui } try { d->dir->modify(guid, merged, sec.get()); d->dir->modify(guid, merged, sec.get(), true); /* merge: keep other attrs */ } catch (std::exception &e2) { err = e2.what(); return false; Loading @@ -720,6 +727,55 @@ bool ActiveDirectory::dirRemove(const std::string &domain, const uuid::uuid &gui return true; } bool ActiveDirectory::dirSetWindowsLogin(const std::string &domain, const uuid::uuid &guid, bool enable, const std::string &clearPassword, std::string &err) { if (g_Replication && g_Replication->isSlave()) { err = "this node is a replication slave (read-only); make changes on the master"; return false; } std::lock_guard<std::mutex> lock(_Lock); Dom *d = domainByName(domain); if (!d) { err = "unknown domain '" + domain + "'"; return false; } auto e = d->dir->byGuid(guid); if (!e) { err = "object not found"; return false; } std::string sam = e->str("sAMAccountName"); if (sam.empty()) { err = "object has no sAMAccountName"; return false; } if (enable) { if (!e->objectSid.empty()) return true; /* already a Windows account */ if (clearPassword.empty()) { err = "a password is required to enable Windows login"; return false; } uint32_t rid = d->dir->allocateRid(); std::string salt = dsdb::kerberosSalt(dsdb::PrincipalKind::User, d->meta.realm, sam, d->meta.dnsName); dsdb::Secrets sec = dsdb::deriveSecrets(clearPassword, salt, 0); dsdb::AttrMap a; a["objectSid"] = {d->meta.sid.withRid(rid).binary()}; dsdb::setInt(a, "sAMAccountType", dsdb::SAM_USER_OBJECT); dsdb::setInt(a, "userAccountControl", dsdb::UAC_NORMAL_ACCOUNT | dsdb::UAC_DONT_EXPIRE_PASSWORD); dsdb::setInt(a, "primaryGroupID", dsdb::RID_DOMAIN_USERS); dsdb::setInt(a, "msDS-KeyVersionNumber", sec.kvno); dsdb::setInt(a, "pwdLastSet", sec.pwdLastSet); if (e->str("userPrincipalName").empty()) a["userPrincipalName"] = {sam + "@" + d->meta.dnsName}; try { d->dir->modify(guid, a, &sec, true); } catch (std::exception &ex) { err = ex.what(); return false; } std::cerr << "[AD] enabled Windows login for '" << sam << "' (RID " << rid << ")" << std::endl; } else { if (e->objectSid.empty()) return true; /* already SID-less */ dsdb::AttrMap a; /* empty value = delete attribute */ a["objectSid"] = {}; a["sAMAccountType"] = {}; a["userAccountControl"] = {}; a["primaryGroupID"] = {}; a["msDS-KeyVersionNumber"] = {}; try { d->dir->modify(guid, a, nullptr, true); } catch (std::exception &ex) { err = ex.what(); return false; } std::cerr << "[AD] disabled Windows login for '" << sam << "' (now SID-less)" << std::endl; } return true; } bool ActiveDirectory::dirAuthenticate(const std::string &domain, const std::string &sam, const std::string &pw, uuid::uuid &outGuid, std::string &err) { Loading Loading @@ -808,8 +864,8 @@ bool ActiveDirectory::dirAddMember(const std::string &domain, const uuid::uuid & ma["memberOf"] = memberOf; try { d->dir->modify(group, ga, nullptr); d->dir->modify(member, ma, nullptr); d->dir->modify(group, ga, nullptr, true); d->dir->modify(member, ma, nullptr, true); } catch (std::exception &e) { err = e.what(); return false; Loading Loading @@ -842,8 +898,8 @@ bool ActiveDirectory::dirRemoveMember(const std::string &domain, const uuid::uui ma["memberOf"] = memberOf; try { d->dir->modify(group, ga, nullptr); d->dir->modify(member, ma, nullptr); d->dir->modify(group, ga, nullptr, true); d->dir->modify(member, ma, nullptr, true); } catch (std::exception &e) { err = e.what(); return false; Loading Loading @@ -919,6 +975,14 @@ bool ActiveDirectory::dirImportNative(const std::string &domain, int &migrated, if (existing) { groupGuid = existing->objectGuid; groupDn = existing->dn.str(); /* repair a group whose attributes an earlier buggy modify wiped */ if (existing->str("sAMAccountName").empty()) { dsdb::AttrMap fix; fix["objectClass"] = {"top", "group"}; fix["sAMAccountName"] = {gname}; fix["cn"] = {gname}; fix["name"] = {gname}; try { d->dir->modify(groupGuid, fix, nullptr, true); } catch (...) {} } } else { dsdb::AttrMap a; a["objectClass"] = {"top", "group"}; Loading Loading @@ -953,14 +1017,14 @@ bool ActiveDirectory::dirImportNative(const std::string &domain, int &migrated, if (std::find(members.begin(), members.end(), mdn) == members.end()) { members.push_back(mdn); dsdb::AttrMap ga = grpEnt->attrs; ga["member"] = members; try { d->dir->modify(groupGuid, ga, nullptr); } catch (...) {} try { d->dir->modify(groupGuid, ga, nullptr, true); } catch (...) {} } } dsdb::Values mo = me->get("memberOf") ? *me->get("memberOf") : dsdb::Values{}; if (std::find(mo.begin(), mo.end(), groupDn) == mo.end()) { mo.push_back(groupDn); dsdb::AttrMap ua = me->attrs; ua["memberOf"] = mo; try { d->dir->modify(me->objectGuid, ua, nullptr); } catch (...) {} try { d->dir->modify(me->objectGuid, ua, nullptr, true); } catch (...) {} } } } Loading
plugin/ad/ad.h +3 −0 Changes for plugin/ad/ad.h: 3 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -283,6 +283,9 @@ namespace fenrirds { bool dirImportNative(const std::string &domain, int &migrated, std::string &err, std::map<std::string, std::string> *groupMap = nullptr) override; bool dirSetWindowsLogin(const std::string &domain, const uuid::uuid &guid, bool enable, const std::string &clearPassword, std::string &err) override; bool dirList(const std::string &domain, int kind, std::vector<dsdb::Entry> &out, std::string &err) override; bool dirGet(const std::string &domain, const uuid::uuid &guid, Loading
src/admin.cpp +39 −0 Changes for src/admin.cpp: 39 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -403,6 +403,45 @@ namespace fenrirds { json_object_put(jobj); } /* ---- JSON API: List Computers (directory objectClass=computer) ---- */ void apiListComputers(libhttppp::HttpRequest &curreq, const char *cdid) { std::string domain = domainNameFor(cdid); if (domain.empty() || !g_AdPlugin) throw AuthBackendError("listComputers: no directory for domain!"); std::vector<dsdb::Entry> ents; std::string err; if (!g_AdPlugin->dirList(domain, AdPlugin::DirComputer, ents, err)) throw AuthBackendError("listComputers: " + err); json_object *jobj = json_object_new_object(); json_object *jarr = json_object_new_array(); for (const auto &e : ents) { json_object *jc = json_object_new_object(); json_object_object_add(jc, "id", json_object_new_string(e.objectGuid.c_str())); json_object_object_add(jc, "name", json_object_new_string(e.str("sAMAccountName").c_str())); json_object_object_add(jc, "dnshostname", json_object_new_string(e.str("dNSHostName").c_str())); json_object_array_add(jarr, jc); } json_object_object_add(jobj, "computers", jarr); sendJson(curreq, jobj); json_object_put(jobj); } /* ---- JSON API: Remove Computer (enables a clean domain rejoin) ---- */ void apiRemoveComputer(libhttppp::HttpRequest &curreq, const char *cdid, const char *cid) { std::string domain = domainNameFor(cdid); if (domain.empty() || !g_AdPlugin) throw AuthBackendError("removeComputer: no directory for domain!"); uuid::uuid gid; gid.parse(cid); std::string err; if (!g_AdPlugin->dirRemove(domain, gid, err)) throw AuthBackendError("removeComputer: " + err); json_object *jobj = json_object_new_object(); json_object_object_add(jobj, "status", json_object_new_string("ok")); sendJson(curreq, jobj); json_object_put(jobj); } /* ---- JSON API: Edit User (directory object) ---- */ void apiEditUser(libhttppp::HttpRequest &curreq, const char *cdid, const char *cuid) { if (strcmp(cdid, "admin") == 0) { Loading
src/dsdb/directory.cpp +12 −1 Changes for src/dsdb/directory.cpp: 12 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -284,7 +284,7 @@ uuid::uuid Directory::create(const Dn &dn, AttrMap attrs, const Secrets *secrets return guid; } void Directory::modify(const uuid::uuid &guid, const AttrMap &attrs, const Secrets *secrets) { void Directory::modify(const uuid::uuid &guid, const AttrMap &attrs, const Secrets *secrets, bool mergeAttrs) { std::lock_guard<std::mutex> lk(_Mtx); ensureFresh(); Dn dn; Loading @@ -292,7 +292,18 @@ void Directory::modify(const uuid::uuid &guid, const AttrMap &attrs, const Secre std::string sblob; if (!loadObject(_Backend, guid, dn, cur, sblob)) throw std::runtime_error("no such object"); if (mergeAttrs) { /* overlay: keep every attribute not named in `attrs`; an empty value * list deletes that attribute */ for (const auto &kv : attrs) { if (kv.second.empty()) cur.erase(kv.first); else cur[kv.first] = kv.second; } } else { cur = attrs; } cur["whenChanged"] = {generalizedTime(unixFromFileTime(fileTimeNow()))}; if (secrets) sblob = secrets->serialize(); Loading
src/dsdb/directory.h +7 −1 Changes for src/dsdb/directory.h: 7 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -117,7 +117,13 @@ namespace fenrirds { * consistent. create allocates objectGUID (if empty) and the SID * for a security principal; returns the new objectGUID. */ uuid::uuid create(const Dn &dn, AttrMap attrs, const Secrets *secrets = nullptr); void modify(const uuid::uuid &guid, const AttrMap &attrs, const Secrets *secrets = nullptr); /* Replace the object's attributes with `attrs` (LDAP semantics). * With mergeAttrs=true, overlay `attrs` onto the existing ones * instead (partial update) — used for member/memberOf and single * field edits so unrelated attributes (sAMAccountName, objectSid…) * are preserved. */ void modify(const uuid::uuid &guid, const AttrMap &attrs, const Secrets *secrets = nullptr, bool mergeAttrs = false); void remove(const uuid::uuid &guid); /* next RID from the domain head's pool */ uint32_t allocateRid(); Loading