Loading dist/adconnect.yaml +6 −2 Changes for dist/adconnect.yaml: 6 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -42,9 +42,13 @@ adconnect: # kdcs: [dc1.example.com, dc2.example.com] # ids: rid (id_base + the RID of the account's SID), rfc2307 (uidNumber # and gidNumber; accounts without them are left out) or guid (the # and gidNumber; accounts without them are left out), guid (the # objectGUID as a GUID id: only programs built for <sys/uid_r.h> see it, # the others the overflow id) # the others the overflow id) or uuid (FenrirDirectoryServer only: its # objectGUID is the account's native UUID, so the id is that UUID). Every # file server of a domain needs the same id_mapping (and id_base): file # owners are stored as these ids. Switching guid -> uuid changes the ids # of all accounts -- see README, "Migrating GUID owners". id_mapping: rid id_base: 200000 id_range: 2000000 Loading src/config.cpp +2 −1 Changes for src/config.cpp: 2 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -252,7 +252,8 @@ void readDomain(const Scope &s, Config &cfg, bool multi, bool &idBaseSet) { if (mapping == "rid") cfg.idMapping = IdMapping::Rid; else if (mapping == "rfc2307") cfg.idMapping = IdMapping::Rfc2307; else if (mapping == "guid") cfg.idMapping = IdMapping::Guid; else throw std::runtime_error(l + "id_mapping: expected rid, rfc2307 or guid, got \"" + mapping + "\""); else if (mapping == "uuid") cfg.idMapping = IdMapping::Uuid; else throw std::runtime_error(l + "id_mapping: expected rid, rfc2307, guid or uuid, got \"" + mapping + "\""); idBaseSet = s.own("id_base"); readNumber(s, "id_base", cfg.idBase, 1000, 0xfffffffeUL); readNumber(s, "id_range", cfg.idRange, 1, 0xfffffffeUL); Loading src/config.h +3 −0 Changes for src/config.h: 3 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -49,6 +49,9 @@ enum class IdMapping { Rid, /* id_base + the RID of the objectSid (classic ids) */ Rfc2307, /* the uidNumber / gidNumber attributes */ Guid, /* the objectGUID as a FenrirOS GUID id (<sys/uid_r.h>) */ Uuid, /* the objectGUID as a FenrirOS UUID id: only for a directory * whose objectGUIDs are native UUIDs (FenrirDirectoryServer * says so in its rootDSE, fenrirObjectGuidIsUuid) */ }; struct Config { Loading src/daemon.cpp +3 −1 Changes for src/daemon.cpp: 3 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -372,7 +372,9 @@ bool resolve(Server &s, Cmd cmd, const std::string &arg, const std::string &line out = End; /* no domain's name */ return true; case Cmd::PwUid: case Cmd::GrGid: { case Cmd::GrGid: case Cmd::SidOf: case Cmd::IdOf: { bool failed = false; for (auto &d : s.domains) { if (!ask(*d, cmd, arg, line, records, error)) { Loading src/directory.cpp +135 −5 Changes for src/directory.cpp: 135 added lines, 5 removed lines. Original line number Diff line number Diff line Loading @@ -193,6 +193,34 @@ bool split(const std::string &b, std::string &domain, uint32_t &rid) { return true; } bool fromString(const std::string &t, std::string &bin) { if (t.size() < 5 || t.compare(0, 4, "S-1-") != 0) return false; std::vector<unsigned long long> parts; size_t p = 4; for (;;) { size_t q = t.find('-', p); std::string part = t.substr(p, q == std::string::npos ? std::string::npos : q - p); if (part.empty() || part.size() > 15) return false; unsigned long long v = 0; for (char c : part) { if (c < '0' || c > '9') return false; v = v * 10 + (unsigned long long)(c - '0'); } parts.push_back(v); if (q == std::string::npos) break; p = q + 1; } if (parts[0] >= 1ULL << 48 || parts.size() - 1 > 15) return false; bin.assign(1, (char)1); bin += (char)(parts.size() - 1); for (int i = 5; i >= 0; i--) bin += (char)(parts[0] >> (8 * i) & 0xff); for (size_t i = 1; i < parts.size(); i++) { if (parts[i] > 0xffffffffULL) return false; for (int k = 0; k < 4; k++) bin += (char)(parts[i] >> (8 * k) & 0xff); } return true; } std::string withRid(const std::string &domain, uint32_t rid) { std::string b = domain; b[1] = (char)((uint8_t)b[1] + 1); Loading Loading @@ -230,6 +258,35 @@ bool guidFromText(const std::string &t, std::string &bin) { return true; } std::string uuidText(const std::string &b) { if (b.size() != 16) return std::string(); const unsigned char *u = (const unsigned char *)b.data(); if ((u[6] & 0xf0) != 0x40 || (u[8] & 0xc0) != 0x80) return std::string(); char buf[40]; snprintf(buf, sizeof buf, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7], u[8], u[9], u[10], u[11], u[12], u[13], u[14], u[15]); return buf; } bool uuidFromText(const std::string &t, std::string &bin) { if (t.size() != 36 || t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-') return false; std::string digits; for (size_t i = 0; i < t.size(); i++) if (i != 8 && i != 13 && i != 18 && i != 23) digits += t[i]; bin.clear(); for (size_t i = 0; i < digits.size(); i += 2) { int v = 0; for (int k = 0; k < 2; k++) { char c = digits[i + k]; int h = c >= '0' && c <= '9' ? c - '0' : c >= 'a' && c <= 'f' ? c - 'a' + 10 : c >= 'A' && c <= 'F' ? c - 'A' + 10 : -1; if (h < 0) return false; v = v << 4 | h; } bin += (char)v; } return !uuidText(bin).empty(); } /* ---- Directory ---- */ std::vector<ldap::Entry> Directory::find(const std::string &filter, const std::vector<std::string> &attrs) { Loading Loading @@ -281,6 +338,20 @@ const std::string &Directory::domainSid() { return _domainSid; } void Directory::nativeUuid() { if (_nativeUuid) return; bool yes = false; _src.search("", ldap::Base, "(objectClass=*)", { "fenrirObjectGuidIsUuid" }, [&](const ldap::Entry &e) { const std::string *v = e.first("fenrirobjectguidisuuid"); yes = v && lower(*v) == "true"; }, 0); if (!yes) throw std::runtime_error("id_mapping: uuid, but " + _cfg.domain + "'s directory doesn't hand out UUIDs (no fenrirObjectGuidIsUuid in its rootDSE)" " -- id_mapping: guid is for other directories"); _nativeUuid = true; } std::string Directory::idOf(const ldap::Entry &e, bool group) { switch (_cfg.idMapping) { case IdMapping::Rid: { Loading @@ -299,6 +370,11 @@ std::string Directory::idOf(const ldap::Entry &e, bool group) { const std::string *g = e.first("objectguid"); return g ? guidText(*g) : std::string(); } case IdMapping::Uuid: { nativeUuid(); const std::string *g = e.first("objectguid"); return g ? uuidText(*g) : std::string(); } } return std::string(); } Loading @@ -315,7 +391,7 @@ std::string Directory::primaryGid(const ldap::Entry &user) { if (_cfg.idMapping == IdMapping::Rid) return rid < _cfg.idRange ? std::to_string(_cfg.idBase + rid) : std::string(); /* Guid: the primary group's objectGUID */ /* Guid, Uuid: the primary group's objectGUID */ auto it = _primaryGids.find((uint32_t)rid); if (it != _primaryGids.end()) return it->second; std::string gid; Loading Loading @@ -344,6 +420,12 @@ std::string Directory::idFilter(const std::string &id, bool group) { if (!guidFromText(id, bin)) return std::string(); return "(objectGUID=" + ldap::escape(bin) + ")"; } case IdMapping::Uuid: { std::string bin; if (!uuidFromText(id, bin)) return std::string(); nativeUuid(); return "(objectGUID=" + ldap::escape(bin) + ")"; } } return std::string(); } Loading Loading @@ -490,10 +572,18 @@ std::string Directory::groupsOf(const std::string &n) { }; add(primaryGid(user)); if (sids.empty()) { /* no tokenGroups (not readable for the bind account): the groups * naming the user as a direct member */ for (const ldap::Entry &g : find(groupFilter("(member=" + ldap::escape(user.dn) + ")"), GroupAttrs)) add(idOf(g, true)); /* no tokenGroups (not readable for the bind account, or a directory * without them): the groups the user is in, nested ones too, by * LDAP_MATCHING_RULE_IN_CHAIN -- only direct memberships where a * server refuses that rule */ std::vector<ldap::Entry> groups; try { groups = find(groupFilter("(member:1.2.840.113556.1.4.1941:=" + ldap::escape(user.dn) + ")"), GroupAttrs); } catch (const ldap::Error &e) { if (e.code() == -1) throw; groups = find(groupFilter("(member=" + ldap::escape(user.dn) + ")"), GroupAttrs); } for (const ldap::Entry &g : groups) add(idOf(g, true)); return out + End; } for (size_t i = 0; i < sids.size(); i += SidsPerSearch) { Loading @@ -506,6 +596,44 @@ std::string Directory::groupsOf(const std::string &n) { return out + End; } /* SIDOF u|g <id>: the objectSid of the account with this id. Only the * account's own id counts (idOf() of the entry found must give it back): * no name matching, no RID arithmetic beyond what id_mapping defines. */ std::string Directory::sidOf(const std::string &arg) { if (arg.size() < 3 || (arg[0] != 'u' && arg[0] != 'g') || arg[1] != ' ') return End; bool group = arg[0] == 'g'; std::string id = arg.substr(2); std::string f = idFilter(id, group); if (f.empty()) return End; for (const ldap::Entry &e : find(group ? groupFilter(f) : userFilter(f), group ? GroupAttrs : UserAttrs)) { User u; Group g; if (group ? !toGroup(e, g) || g.gid != id : !toUser(e, u) || u.uid != id) continue; const std::string *s = e.first("objectsid"); if (!s || !sidValid(*s)) return End; /* an account without one: its S-1-22 SID stays */ return formatSid(SidMap{ group, id, sid::toString(*s) }) + End; } return End; } /* IDOF <SID>: the id of the user or group with this objectSid -- only SIDs * of our domain are looked up */ std::string Directory::idOfSid(const std::string &text) { std::string bin, domain; uint32_t rid; if (!sid::fromString(text, bin) || !sid::split(bin, domain, rid) || domain != domainSid()) return End; std::string f = "(objectSid=" + ldap::escape(bin) + ")"; for (const ldap::Entry &e : find(userFilter(f), UserAttrs)) { User u; if (toUser(e, u)) return formatSid(SidMap{ false, u.uid, sid::toString(bin) }) + End; } for (const ldap::Entry &e : find(groupFilter(f), GroupAttrs)) { Group g; if (toGroup(e, g)) return formatSid(SidMap{ true, g.gid, sid::toString(bin) }) + End; } return End; } std::string Directory::handle(Cmd cmd, const std::string &arg) { switch (cmd) { case Cmd::PwNam: return userByName(arg); Loading @@ -515,6 +643,8 @@ std::string Directory::handle(Cmd cmd, const std::string &arg) { case Cmd::GrGid: return groupById(arg); case Cmd::GrEnt: return allGroups(); case Cmd::Groups: return groupsOf(arg); case Cmd::SidOf: return sidOf(arg); case Cmd::IdOf: return idOfSid(arg); case Cmd::Krb5: case Cmd::Ntlm: case Cmd::Auth: Loading Loading
dist/adconnect.yaml +6 −2 Changes for dist/adconnect.yaml: 6 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -42,9 +42,13 @@ adconnect: # kdcs: [dc1.example.com, dc2.example.com] # ids: rid (id_base + the RID of the account's SID), rfc2307 (uidNumber # and gidNumber; accounts without them are left out) or guid (the # and gidNumber; accounts without them are left out), guid (the # objectGUID as a GUID id: only programs built for <sys/uid_r.h> see it, # the others the overflow id) # the others the overflow id) or uuid (FenrirDirectoryServer only: its # objectGUID is the account's native UUID, so the id is that UUID). Every # file server of a domain needs the same id_mapping (and id_base): file # owners are stored as these ids. Switching guid -> uuid changes the ids # of all accounts -- see README, "Migrating GUID owners". id_mapping: rid id_base: 200000 id_range: 2000000 Loading
src/config.cpp +2 −1 Changes for src/config.cpp: 2 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -252,7 +252,8 @@ void readDomain(const Scope &s, Config &cfg, bool multi, bool &idBaseSet) { if (mapping == "rid") cfg.idMapping = IdMapping::Rid; else if (mapping == "rfc2307") cfg.idMapping = IdMapping::Rfc2307; else if (mapping == "guid") cfg.idMapping = IdMapping::Guid; else throw std::runtime_error(l + "id_mapping: expected rid, rfc2307 or guid, got \"" + mapping + "\""); else if (mapping == "uuid") cfg.idMapping = IdMapping::Uuid; else throw std::runtime_error(l + "id_mapping: expected rid, rfc2307, guid or uuid, got \"" + mapping + "\""); idBaseSet = s.own("id_base"); readNumber(s, "id_base", cfg.idBase, 1000, 0xfffffffeUL); readNumber(s, "id_range", cfg.idRange, 1, 0xfffffffeUL); Loading
src/config.h +3 −0 Changes for src/config.h: 3 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -49,6 +49,9 @@ enum class IdMapping { Rid, /* id_base + the RID of the objectSid (classic ids) */ Rfc2307, /* the uidNumber / gidNumber attributes */ Guid, /* the objectGUID as a FenrirOS GUID id (<sys/uid_r.h>) */ Uuid, /* the objectGUID as a FenrirOS UUID id: only for a directory * whose objectGUIDs are native UUIDs (FenrirDirectoryServer * says so in its rootDSE, fenrirObjectGuidIsUuid) */ }; struct Config { Loading
src/daemon.cpp +3 −1 Changes for src/daemon.cpp: 3 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -372,7 +372,9 @@ bool resolve(Server &s, Cmd cmd, const std::string &arg, const std::string &line out = End; /* no domain's name */ return true; case Cmd::PwUid: case Cmd::GrGid: { case Cmd::GrGid: case Cmd::SidOf: case Cmd::IdOf: { bool failed = false; for (auto &d : s.domains) { if (!ask(*d, cmd, arg, line, records, error)) { Loading
src/directory.cpp +135 −5 Changes for src/directory.cpp: 135 added lines, 5 removed lines. Original line number Diff line number Diff line Loading @@ -193,6 +193,34 @@ bool split(const std::string &b, std::string &domain, uint32_t &rid) { return true; } bool fromString(const std::string &t, std::string &bin) { if (t.size() < 5 || t.compare(0, 4, "S-1-") != 0) return false; std::vector<unsigned long long> parts; size_t p = 4; for (;;) { size_t q = t.find('-', p); std::string part = t.substr(p, q == std::string::npos ? std::string::npos : q - p); if (part.empty() || part.size() > 15) return false; unsigned long long v = 0; for (char c : part) { if (c < '0' || c > '9') return false; v = v * 10 + (unsigned long long)(c - '0'); } parts.push_back(v); if (q == std::string::npos) break; p = q + 1; } if (parts[0] >= 1ULL << 48 || parts.size() - 1 > 15) return false; bin.assign(1, (char)1); bin += (char)(parts.size() - 1); for (int i = 5; i >= 0; i--) bin += (char)(parts[0] >> (8 * i) & 0xff); for (size_t i = 1; i < parts.size(); i++) { if (parts[i] > 0xffffffffULL) return false; for (int k = 0; k < 4; k++) bin += (char)(parts[i] >> (8 * k) & 0xff); } return true; } std::string withRid(const std::string &domain, uint32_t rid) { std::string b = domain; b[1] = (char)((uint8_t)b[1] + 1); Loading Loading @@ -230,6 +258,35 @@ bool guidFromText(const std::string &t, std::string &bin) { return true; } std::string uuidText(const std::string &b) { if (b.size() != 16) return std::string(); const unsigned char *u = (const unsigned char *)b.data(); if ((u[6] & 0xf0) != 0x40 || (u[8] & 0xc0) != 0x80) return std::string(); char buf[40]; snprintf(buf, sizeof buf, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7], u[8], u[9], u[10], u[11], u[12], u[13], u[14], u[15]); return buf; } bool uuidFromText(const std::string &t, std::string &bin) { if (t.size() != 36 || t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-') return false; std::string digits; for (size_t i = 0; i < t.size(); i++) if (i != 8 && i != 13 && i != 18 && i != 23) digits += t[i]; bin.clear(); for (size_t i = 0; i < digits.size(); i += 2) { int v = 0; for (int k = 0; k < 2; k++) { char c = digits[i + k]; int h = c >= '0' && c <= '9' ? c - '0' : c >= 'a' && c <= 'f' ? c - 'a' + 10 : c >= 'A' && c <= 'F' ? c - 'A' + 10 : -1; if (h < 0) return false; v = v << 4 | h; } bin += (char)v; } return !uuidText(bin).empty(); } /* ---- Directory ---- */ std::vector<ldap::Entry> Directory::find(const std::string &filter, const std::vector<std::string> &attrs) { Loading Loading @@ -281,6 +338,20 @@ const std::string &Directory::domainSid() { return _domainSid; } void Directory::nativeUuid() { if (_nativeUuid) return; bool yes = false; _src.search("", ldap::Base, "(objectClass=*)", { "fenrirObjectGuidIsUuid" }, [&](const ldap::Entry &e) { const std::string *v = e.first("fenrirobjectguidisuuid"); yes = v && lower(*v) == "true"; }, 0); if (!yes) throw std::runtime_error("id_mapping: uuid, but " + _cfg.domain + "'s directory doesn't hand out UUIDs (no fenrirObjectGuidIsUuid in its rootDSE)" " -- id_mapping: guid is for other directories"); _nativeUuid = true; } std::string Directory::idOf(const ldap::Entry &e, bool group) { switch (_cfg.idMapping) { case IdMapping::Rid: { Loading @@ -299,6 +370,11 @@ std::string Directory::idOf(const ldap::Entry &e, bool group) { const std::string *g = e.first("objectguid"); return g ? guidText(*g) : std::string(); } case IdMapping::Uuid: { nativeUuid(); const std::string *g = e.first("objectguid"); return g ? uuidText(*g) : std::string(); } } return std::string(); } Loading @@ -315,7 +391,7 @@ std::string Directory::primaryGid(const ldap::Entry &user) { if (_cfg.idMapping == IdMapping::Rid) return rid < _cfg.idRange ? std::to_string(_cfg.idBase + rid) : std::string(); /* Guid: the primary group's objectGUID */ /* Guid, Uuid: the primary group's objectGUID */ auto it = _primaryGids.find((uint32_t)rid); if (it != _primaryGids.end()) return it->second; std::string gid; Loading Loading @@ -344,6 +420,12 @@ std::string Directory::idFilter(const std::string &id, bool group) { if (!guidFromText(id, bin)) return std::string(); return "(objectGUID=" + ldap::escape(bin) + ")"; } case IdMapping::Uuid: { std::string bin; if (!uuidFromText(id, bin)) return std::string(); nativeUuid(); return "(objectGUID=" + ldap::escape(bin) + ")"; } } return std::string(); } Loading Loading @@ -490,10 +572,18 @@ std::string Directory::groupsOf(const std::string &n) { }; add(primaryGid(user)); if (sids.empty()) { /* no tokenGroups (not readable for the bind account): the groups * naming the user as a direct member */ for (const ldap::Entry &g : find(groupFilter("(member=" + ldap::escape(user.dn) + ")"), GroupAttrs)) add(idOf(g, true)); /* no tokenGroups (not readable for the bind account, or a directory * without them): the groups the user is in, nested ones too, by * LDAP_MATCHING_RULE_IN_CHAIN -- only direct memberships where a * server refuses that rule */ std::vector<ldap::Entry> groups; try { groups = find(groupFilter("(member:1.2.840.113556.1.4.1941:=" + ldap::escape(user.dn) + ")"), GroupAttrs); } catch (const ldap::Error &e) { if (e.code() == -1) throw; groups = find(groupFilter("(member=" + ldap::escape(user.dn) + ")"), GroupAttrs); } for (const ldap::Entry &g : groups) add(idOf(g, true)); return out + End; } for (size_t i = 0; i < sids.size(); i += SidsPerSearch) { Loading @@ -506,6 +596,44 @@ std::string Directory::groupsOf(const std::string &n) { return out + End; } /* SIDOF u|g <id>: the objectSid of the account with this id. Only the * account's own id counts (idOf() of the entry found must give it back): * no name matching, no RID arithmetic beyond what id_mapping defines. */ std::string Directory::sidOf(const std::string &arg) { if (arg.size() < 3 || (arg[0] != 'u' && arg[0] != 'g') || arg[1] != ' ') return End; bool group = arg[0] == 'g'; std::string id = arg.substr(2); std::string f = idFilter(id, group); if (f.empty()) return End; for (const ldap::Entry &e : find(group ? groupFilter(f) : userFilter(f), group ? GroupAttrs : UserAttrs)) { User u; Group g; if (group ? !toGroup(e, g) || g.gid != id : !toUser(e, u) || u.uid != id) continue; const std::string *s = e.first("objectsid"); if (!s || !sidValid(*s)) return End; /* an account without one: its S-1-22 SID stays */ return formatSid(SidMap{ group, id, sid::toString(*s) }) + End; } return End; } /* IDOF <SID>: the id of the user or group with this objectSid -- only SIDs * of our domain are looked up */ std::string Directory::idOfSid(const std::string &text) { std::string bin, domain; uint32_t rid; if (!sid::fromString(text, bin) || !sid::split(bin, domain, rid) || domain != domainSid()) return End; std::string f = "(objectSid=" + ldap::escape(bin) + ")"; for (const ldap::Entry &e : find(userFilter(f), UserAttrs)) { User u; if (toUser(e, u)) return formatSid(SidMap{ false, u.uid, sid::toString(bin) }) + End; } for (const ldap::Entry &e : find(groupFilter(f), GroupAttrs)) { Group g; if (toGroup(e, g)) return formatSid(SidMap{ true, g.gid, sid::toString(bin) }) + End; } return End; } std::string Directory::handle(Cmd cmd, const std::string &arg) { switch (cmd) { case Cmd::PwNam: return userByName(arg); Loading @@ -515,6 +643,8 @@ std::string Directory::handle(Cmd cmd, const std::string &arg) { case Cmd::GrGid: return groupById(arg); case Cmd::GrEnt: return allGroups(); case Cmd::Groups: return groupsOf(arg); case Cmd::SidOf: return sidOf(arg); case Cmd::IdOf: return idOfSid(arg); case Cmd::Krb5: case Cmd::Ntlm: case Cmd::Auth: Loading