Loading plugin/krb5/krb5.cpp +13 −2 Changes for plugin/krb5/krb5.cpp: 13 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -2811,8 +2811,13 @@ RealmInfo *Krb5KDC::resolveRealm(const std::string &realm) { auto it = _Realms.find(toUpperRealm(realm)); if (it == _Realms.end()) it = _Realms.find(realm); if (it == _Realms.end()) if (it == _Realms.end()) { for (auto &kv : _Realms) if (!kv.second.meta.netbios.empty() && toUpperRealm(kv.second.meta.netbios) == toUpperRealm(realm)) return &kv.second; return nullptr; } return &it->second; } Loading @@ -2822,7 +2827,13 @@ void Krb5KDC::canonicalizeRealms(Krb5KdcReq &req) { auto canon = [&](std::string &r) { if (_Realms.count(r)) return; std::string u = toUpperRealm(r); if (_Realms.count(u)) r = u; if (_Realms.count(u)) { r = u; return; } /* the NetBIOS domain name (EXAMPLE\user logons) */ for (auto &kv : _Realms) if (!kv.second.meta.netbios.empty() && toUpperRealm(kv.second.meta.netbios) == u) { r = kv.first; return; } }; canon(req.cname.realm); canon(req.sname.realm); Loading src/rpc/netlogon.cpp +261 −0 Changes for src/rpc/netlogon.cpp: 261 added lines, 0 removed lines. Original line number Diff line number Diff line #include <cstring> #include <iostream> #include <map> #include <optional> #include <mutex> Loading @@ -16,6 +17,7 @@ namespace rpc { namespace { enum { NETR_SERVERREQCHALLENGE = 4, NETR_LOGONSAMLOGON = 2, NETR_SERVERAUTHENTICATE = 5, NETR_SERVERAUTHENTICATE2 = 15, NETR_SERVERAUTHENTICATE3 = 26, Loading @@ -29,6 +31,7 @@ namespace { DSR_GETDCNAMEEX2 = 34, NETR_LOGONSAMLOGONEX = 39, DSR_ENUMDOMAINTRUSTS = 40, /* DsrEnumerateDomainTrusts */ NETR_LOGONSAMLOGONWITHFLAGS = 45, }; std::mutex g_dcMtx; Loading @@ -51,6 +54,10 @@ namespace { constexpr uint32_t ST_INVALID_LEVEL = 0xC0000148; constexpr uint32_t ST_INVALID_PARAMETER = 0xC000000D; constexpr uint32_t WERR_NO_SUCH_DOMAIN = 1355; constexpr uint32_t ST_NO_SUCH_USER = 0xC0000064; constexpr uint32_t ST_WRONG_PASSWORD = 0xC000006A; constexpr uint32_t ST_ACCOUNT_DISABLED = 0xC0000072; constexpr uint32_t ST_INVALID_INFO_CLASS = 0xC0000003; /* per-computer secure-channel state. seed is the stored credential * the authenticator chain steps from (MS-NRPC 3.1.4.5). */ Loading Loading @@ -242,6 +249,199 @@ namespace { if (usPtr[1]) w.osName = in.unistrBuffer(); return w; } /* ---- NTLM pass-through (NetrLogonSamLogon*) ---- */ struct Identity { std::string domain, account, workstation; }; /* netr_IdentityInfo fixed part; strings are read later in pointer order */ struct IdentityPtrs { uint32_t dom = 0, acct = 0, ws = 0; }; IdentityPtrs pullIdentity(NdrPull &in) { IdentityPtrs p; uint16_t l, m; p.dom = in.unistrHeader(l, m); in.u32(); in.u32(); in.u32(); /* parameter_control, logon_id */ p.acct = in.unistrHeader(l, m); p.ws = in.unistrHeader(l, m); return p; } Identity pullIdentityStrings(NdrPull &in, const IdentityPtrs &p) { Identity id; if (p.dom) id.domain = in.unistrBuffer(); if (p.acct) id.account = in.unistrBuffer(); if (p.ws) id.workstation = in.unistrBuffer(); return id; } crypto::Bytes pullBytesBuffer(NdrPull &in) { in.align(4); in.u32(); in.u32(); /* max, offset */ uint32_t n = in.u32(); return in.takeVec(n); } struct LogonRequest { uint16_t level = 0; /* 1/5 interactive, 2/6 network */ Identity id; crypto::Bytes lmOwf, ntOwf; /* interactive (encrypted) */ crypto::Bytes challenge, ntResp, lmResp; /* network */ }; /* the [ref] netr_LogonLevel union and what it points to */ bool pullLogon(NdrPull &in, LogonRequest &r) { r.level = in.u16(); in.u16(); /* union discriminant */ if (in.u32() == 0) return false; IdentityPtrs ip = pullIdentity(in); if (r.level == 1 || r.level == 5) { r.lmOwf = in.takeVec(16); r.ntOwf = in.takeVec(16); r.id = pullIdentityStrings(in, ip); return true; } if (r.level == 2 || r.level == 6) { r.challenge = in.takeVec(8); uint16_t l, m; l = in.u16(); m = in.u16(); uint32_t ntp = in.u32(); l = in.u16(); m = in.u16(); uint32_t lmp = in.u32(); (void)l; (void)m; r.id = pullIdentityStrings(in, ip); if (ntp) r.ntResp = pullBytesBuffer(in); if (lmp) r.lmResp = pullBytesBuffer(in); return true; } return false; } std::string upperU(const std::string &s) { return dsdb::upper(s); } /* verify a logon against the account; sessionKey: NTLM user session key */ uint32_t checkLogon(dsdb::Directory &dir, const LogonRequest &r, const crypto::Bytes &channelKey, std::optional<dsdb::Entry> &acct, crypto::Bytes &userSessionKey) { std::string name = r.id.account; acct = dir.bySamAccountName(name); if (!acct) acct = dir.byUpn(name); if (!acct && name.find('@') != std::string::npos) acct = dir.bySamAccountName(name.substr(0, name.find('@'))); if (!acct) return ST_NO_SUCH_USER; dsdb::Secrets sec = dir.secretsOf(acct->objectGuid); if (sec.ntHash.size() != 16) return ST_NO_SUCH_USER; crypto::Bytes nt(sec.ntHash.begin(), sec.ntHash.end()); bool ok = false; if (r.level == 1 || r.level == 5) { /* the OWFs come AES-CFB8 encrypted with the channel key, zero IV */ crypto::Bytes owf = crypto::aesCfb8Decrypt(channelKey, crypto::Bytes(16, 0), r.ntOwf); ok = crypto::equal(owf, nt); } else if (r.ntResp.size() > 24) { /* NTLMv2: NTProofStr = HMAC-MD5(NTOWFv2, challenge || blob) */ crypto::Bytes proof(r.ntResp.begin(), r.ntResp.begin() + 16); crypto::Bytes blob(r.ntResp.begin() + 16, r.ntResp.end()); for (const std::string &dom : {r.id.domain, std::string()}) { crypto::Bytes v2 = crypto::hmacMd5(nt, crypto::utf16le(upperU(r.id.account) + dom)); crypto::Bytes want = crypto::hmacMd5(v2, crypto::concat(r.challenge, blob)); want.resize(16); if (crypto::equal(want, proof)) { userSessionKey = crypto::hmacMd5(v2, proof); ok = true; break; } } } std::cerr << "[NETLOGON] SamLogon level=" << r.level << " '" << r.id.domain << "\\" << r.id.account << "' from '" << r.id.workstation << "' -> " << (ok ? "OK" : "WRONG PASSWORD") << std::endl; if (!ok) return ST_WRONG_PASSWORD; if (dsdb::intValue(acct->attrs, "userAccountControl", 0) & 0x2) return ST_ACCOUNT_DISABLED; return ST_OK; } /* the account's in-domain group RIDs, nested groups included */ std::vector<uint32_t> groupRids(dsdb::Directory &dir, const dsdb::Entry &e, const dsdb::Sid &domSid, uint32_t primary) { std::map<uint32_t, bool> rids; rids[primary] = true; std::map<std::string, bool> seen; std::vector<dsdb::Values> todo; if (auto *mo = e.get("memberOf")) todo.push_back(*mo); while (!todo.empty()) { dsdb::Values mo = todo.back(); todo.pop_back(); for (auto &dnStr : mo) { dsdb::Dn gdn; if (!dsdb::Dn::parse(dnStr, gdn) || seen.count(gdn.key())) continue; seen[gdn.key()] = true; auto g = dir.byDn(gdn); if (!g || g->objectSid.empty() || !g->objectSid.inDomain(domSid)) continue; rids[g->objectSid.rid()] = true; if (auto *outer = g->get("memberOf")) todo.push_back(*outer); } } std::vector<uint32_t> out; for (auto &kv : rids) out.push_back(kv.first); return out; } /* netr_SamInfo2/3/6 (MS-NRPC NETLOGON_VALIDATION_SAM_INFO*) */ void pushValidation(NdrPush &p, uint16_t level, dsdb::Directory &dir, const dsdb::DomainMeta &m, const dsdb::Entry &e, const crypto::Bytes &userKey) { dsdb::Sid domSid = m.sid; if (auto head = dir.byDn(m.baseDn)) if (!head->objectSid.empty()) domSid = head->objectSid; uint32_t primary = uint32_t(dsdb::intValue(e.attrs, "primaryGroupID", dsdb::RID_DOMAIN_USERS)); std::vector<uint32_t> groups = groupRids(dir, e, domSid, primary); int64_t now = dsdb::fileTimeNow(), pwd = dsdb::intValue(e.attrs, "pwdLastSet", 0); const int64_t never = 0x7fffffffffffffffLL; std::string host; { std::lock_guard<std::mutex> l(g_dcMtx); host = g_dcHost; } std::string server = dsdb::upper(host.substr(0, host.find('.'))); std::string sam = e.str("sAMAccountName"), full = e.str("displayName"); std::string script = e.str("scriptPath"), profile = e.str("profilePath"); std::string homeDir = e.str("homeDirectory"), homeDrive = e.str("homeDrive"); uint32_t uac = uint32_t(dsdb::intValue(e.attrs, "userAccountControl", 0x200)); dsdb::Sid uuidSid = dsdb::uuidUserSid(e.objectGuid.value); bool sids = level != 2; p.align(8); for (int64_t t : {now, never, never, pwd, pwd, never}) p.u64(uint64_t(t)); for (auto *str : {&sam, &full, &script, &profile, &homeDir, &homeDrive}) p.unistrHeader(*str); p.u16(0); p.u16(0); /* logon_count, bad_password_count */ p.u32(e.objectSid.empty() ? 0 : e.objectSid.rid()); p.u32(primary); p.u32(uint32_t(groups.size())); p.ref(!groups.empty()); p.u32(sids ? 0x20 : 0); /* user_flags: NETLOGON_EXTRA_SIDS */ p.bytes(userKey.size() == 16 ? userKey : crypto::Bytes(16, 0)); p.unistrHeader(server); p.unistrHeader(m.netbios); p.ref(true); /* domain_sid */ p.bytes(std::vector<uint8_t>(8, 0)); /* LMSessKey */ p.u32((uac & 0x1000) ? 0x80 : (uac & 0x2000) ? 0x100 : 0x10); /* ACB_WSTRUST/SVRTRUST/NORMAL */ p.u32(0); /* sub_auth_status */ p.u64(uint64_t(now)); p.u64(0); /* last_successful/failed_logon */ p.u32(0); p.u32(0); /* failed_logon_count, reserved */ if (sids) { p.u32(1); p.ref(true); } /* sidcount, sids */ std::string upn = e.str("userPrincipalName"); if (upn.empty()) upn = sam + "@" + m.dnsName; if (level == 6) { p.unistrHeader(m.dnsName); p.unistrHeader(upn); for (int i = 0; i < 20; ++i) p.u32(0); } /* deferred */ for (auto *str : {&sam, &full, &script, &profile, &homeDir, &homeDrive}) p.unistrBuffer(*str); if (!groups.empty()) { p.u32(uint32_t(groups.size())); for (uint32_t g : groups) { p.u32(g); p.u32(7); } } p.unistrBuffer(server); p.unistrBuffer(m.netbios); p.sid(domSid); if (sids) { p.u32(1); p.ref(true); p.u32(7); /* netr_SidAttr */ p.sid(uuidSid); } if (level == 6) { p.unistrBuffer(m.dnsName); p.unistrBuffer(upn); } } } void setDcIdentity(const std::string &host, const std::string &address) { Loading Loading @@ -522,6 +722,67 @@ DispatchResult netlogonDispatch(uint16_t opnum, const std::vector<uint8_t> &stub p.u32(0); return DispatchResult{ST_OK, false, 0, p.buf}; } case NETR_LOGONSAMLOGON: case NETR_LOGONSAMLOGONEX: case NETR_LOGONSAMLOGONWITHFLAGS: { NdrPull in(stub); pullStr(in); /* LogonServer */ std::string computer = pullStr(in); if (computer.empty()) computer = schannelComputer; bool withAuth = opnum != NETR_LOGONSAMLOGONEX; Authenticator a; bool haveAuth = false; if (withAuth) { if (in.u32()) { a = pullAuth(in); haveAuth = true; } if (in.u32()) pullAuth(in); /* ReturnAuthenticator (in) */ } LogonRequest lr; bool parsed = pullLogon(in, lr); uint16_t vlevel = in.u16(); uint32_t flags = 0; if (opnum != NETR_LOGONSAMLOGON) flags = in.u32(); NdrPush p; auto finish = [&](uint32_t st) { if (st != ST_OK) { p.u16(vlevel); p.ref(false); } p.u8(1); /* Authoritative */ if (opnum != NETR_LOGONSAMLOGON) p.u32(flags); p.u32(st); return DispatchResult{st, false, 0, p.buf}; }; /* the secure channel: an authenticator, or (Ex) the schannel bind */ Chan ch; crypto::Bytes ret; bool chanOk = false; if (withAuth) { chanOk = haveAuth && stepCredential(computer, a, ret, &ch); p.ref(true); pushAuth(p, chanOk ? ret : crypto::Bytes()); } else if (!schannelComputer.empty()) { std::lock_guard<std::mutex> l(g_mtx); auto it = g_chans.find(dsdb::lower(schannelComputer)); if (it != g_chans.end()) { ch = it->second; chanOk = true; } } if (!chanOk) return finish(ST_ACCESS); if (!parsed) return finish(ST_INVALID_INFO_CLASS); if (vlevel != 2 && vlevel != 3 && vlevel != 6) return finish(ST_INVALID_INFO_CLASS); uint32_t st = ST_NO_SUCH_USER; withDir([&](dsdb::Directory &dir, const dsdb::DomainMeta &m) { std::optional<dsdb::Entry> acct; crypto::Bytes userKey; st = checkLogon(dir, lr, ch.sk, acct, userKey); if (st != ST_OK) return true; /* session keys travel encrypted with the channel key (not * in level 6, which is only used over a sealed channel) */ if (vlevel != 6 && userKey.size() == 16) userKey = crypto::aesCfb8Encrypt(ch.sk, crypto::Bytes(16, 0), userKey); p.u16(vlevel); p.ref(true); pushValidation(p, vlevel, dir, m, *acct, userKey); return true; }); return finish(st); } case DSR_ENUMDOMAINTRUSTS: { /* return this domain as the sole (primary, in-forest) trust so the * client can build its domain context at boot/logon. */ Loading Loading
plugin/krb5/krb5.cpp +13 −2 Changes for plugin/krb5/krb5.cpp: 13 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -2811,8 +2811,13 @@ RealmInfo *Krb5KDC::resolveRealm(const std::string &realm) { auto it = _Realms.find(toUpperRealm(realm)); if (it == _Realms.end()) it = _Realms.find(realm); if (it == _Realms.end()) if (it == _Realms.end()) { for (auto &kv : _Realms) if (!kv.second.meta.netbios.empty() && toUpperRealm(kv.second.meta.netbios) == toUpperRealm(realm)) return &kv.second; return nullptr; } return &it->second; } Loading @@ -2822,7 +2827,13 @@ void Krb5KDC::canonicalizeRealms(Krb5KdcReq &req) { auto canon = [&](std::string &r) { if (_Realms.count(r)) return; std::string u = toUpperRealm(r); if (_Realms.count(u)) r = u; if (_Realms.count(u)) { r = u; return; } /* the NetBIOS domain name (EXAMPLE\user logons) */ for (auto &kv : _Realms) if (!kv.second.meta.netbios.empty() && toUpperRealm(kv.second.meta.netbios) == u) { r = kv.first; return; } }; canon(req.cname.realm); canon(req.sname.realm); Loading
src/rpc/netlogon.cpp +261 −0 Changes for src/rpc/netlogon.cpp: 261 added lines, 0 removed lines. Original line number Diff line number Diff line #include <cstring> #include <iostream> #include <map> #include <optional> #include <mutex> Loading @@ -16,6 +17,7 @@ namespace rpc { namespace { enum { NETR_SERVERREQCHALLENGE = 4, NETR_LOGONSAMLOGON = 2, NETR_SERVERAUTHENTICATE = 5, NETR_SERVERAUTHENTICATE2 = 15, NETR_SERVERAUTHENTICATE3 = 26, Loading @@ -29,6 +31,7 @@ namespace { DSR_GETDCNAMEEX2 = 34, NETR_LOGONSAMLOGONEX = 39, DSR_ENUMDOMAINTRUSTS = 40, /* DsrEnumerateDomainTrusts */ NETR_LOGONSAMLOGONWITHFLAGS = 45, }; std::mutex g_dcMtx; Loading @@ -51,6 +54,10 @@ namespace { constexpr uint32_t ST_INVALID_LEVEL = 0xC0000148; constexpr uint32_t ST_INVALID_PARAMETER = 0xC000000D; constexpr uint32_t WERR_NO_SUCH_DOMAIN = 1355; constexpr uint32_t ST_NO_SUCH_USER = 0xC0000064; constexpr uint32_t ST_WRONG_PASSWORD = 0xC000006A; constexpr uint32_t ST_ACCOUNT_DISABLED = 0xC0000072; constexpr uint32_t ST_INVALID_INFO_CLASS = 0xC0000003; /* per-computer secure-channel state. seed is the stored credential * the authenticator chain steps from (MS-NRPC 3.1.4.5). */ Loading Loading @@ -242,6 +249,199 @@ namespace { if (usPtr[1]) w.osName = in.unistrBuffer(); return w; } /* ---- NTLM pass-through (NetrLogonSamLogon*) ---- */ struct Identity { std::string domain, account, workstation; }; /* netr_IdentityInfo fixed part; strings are read later in pointer order */ struct IdentityPtrs { uint32_t dom = 0, acct = 0, ws = 0; }; IdentityPtrs pullIdentity(NdrPull &in) { IdentityPtrs p; uint16_t l, m; p.dom = in.unistrHeader(l, m); in.u32(); in.u32(); in.u32(); /* parameter_control, logon_id */ p.acct = in.unistrHeader(l, m); p.ws = in.unistrHeader(l, m); return p; } Identity pullIdentityStrings(NdrPull &in, const IdentityPtrs &p) { Identity id; if (p.dom) id.domain = in.unistrBuffer(); if (p.acct) id.account = in.unistrBuffer(); if (p.ws) id.workstation = in.unistrBuffer(); return id; } crypto::Bytes pullBytesBuffer(NdrPull &in) { in.align(4); in.u32(); in.u32(); /* max, offset */ uint32_t n = in.u32(); return in.takeVec(n); } struct LogonRequest { uint16_t level = 0; /* 1/5 interactive, 2/6 network */ Identity id; crypto::Bytes lmOwf, ntOwf; /* interactive (encrypted) */ crypto::Bytes challenge, ntResp, lmResp; /* network */ }; /* the [ref] netr_LogonLevel union and what it points to */ bool pullLogon(NdrPull &in, LogonRequest &r) { r.level = in.u16(); in.u16(); /* union discriminant */ if (in.u32() == 0) return false; IdentityPtrs ip = pullIdentity(in); if (r.level == 1 || r.level == 5) { r.lmOwf = in.takeVec(16); r.ntOwf = in.takeVec(16); r.id = pullIdentityStrings(in, ip); return true; } if (r.level == 2 || r.level == 6) { r.challenge = in.takeVec(8); uint16_t l, m; l = in.u16(); m = in.u16(); uint32_t ntp = in.u32(); l = in.u16(); m = in.u16(); uint32_t lmp = in.u32(); (void)l; (void)m; r.id = pullIdentityStrings(in, ip); if (ntp) r.ntResp = pullBytesBuffer(in); if (lmp) r.lmResp = pullBytesBuffer(in); return true; } return false; } std::string upperU(const std::string &s) { return dsdb::upper(s); } /* verify a logon against the account; sessionKey: NTLM user session key */ uint32_t checkLogon(dsdb::Directory &dir, const LogonRequest &r, const crypto::Bytes &channelKey, std::optional<dsdb::Entry> &acct, crypto::Bytes &userSessionKey) { std::string name = r.id.account; acct = dir.bySamAccountName(name); if (!acct) acct = dir.byUpn(name); if (!acct && name.find('@') != std::string::npos) acct = dir.bySamAccountName(name.substr(0, name.find('@'))); if (!acct) return ST_NO_SUCH_USER; dsdb::Secrets sec = dir.secretsOf(acct->objectGuid); if (sec.ntHash.size() != 16) return ST_NO_SUCH_USER; crypto::Bytes nt(sec.ntHash.begin(), sec.ntHash.end()); bool ok = false; if (r.level == 1 || r.level == 5) { /* the OWFs come AES-CFB8 encrypted with the channel key, zero IV */ crypto::Bytes owf = crypto::aesCfb8Decrypt(channelKey, crypto::Bytes(16, 0), r.ntOwf); ok = crypto::equal(owf, nt); } else if (r.ntResp.size() > 24) { /* NTLMv2: NTProofStr = HMAC-MD5(NTOWFv2, challenge || blob) */ crypto::Bytes proof(r.ntResp.begin(), r.ntResp.begin() + 16); crypto::Bytes blob(r.ntResp.begin() + 16, r.ntResp.end()); for (const std::string &dom : {r.id.domain, std::string()}) { crypto::Bytes v2 = crypto::hmacMd5(nt, crypto::utf16le(upperU(r.id.account) + dom)); crypto::Bytes want = crypto::hmacMd5(v2, crypto::concat(r.challenge, blob)); want.resize(16); if (crypto::equal(want, proof)) { userSessionKey = crypto::hmacMd5(v2, proof); ok = true; break; } } } std::cerr << "[NETLOGON] SamLogon level=" << r.level << " '" << r.id.domain << "\\" << r.id.account << "' from '" << r.id.workstation << "' -> " << (ok ? "OK" : "WRONG PASSWORD") << std::endl; if (!ok) return ST_WRONG_PASSWORD; if (dsdb::intValue(acct->attrs, "userAccountControl", 0) & 0x2) return ST_ACCOUNT_DISABLED; return ST_OK; } /* the account's in-domain group RIDs, nested groups included */ std::vector<uint32_t> groupRids(dsdb::Directory &dir, const dsdb::Entry &e, const dsdb::Sid &domSid, uint32_t primary) { std::map<uint32_t, bool> rids; rids[primary] = true; std::map<std::string, bool> seen; std::vector<dsdb::Values> todo; if (auto *mo = e.get("memberOf")) todo.push_back(*mo); while (!todo.empty()) { dsdb::Values mo = todo.back(); todo.pop_back(); for (auto &dnStr : mo) { dsdb::Dn gdn; if (!dsdb::Dn::parse(dnStr, gdn) || seen.count(gdn.key())) continue; seen[gdn.key()] = true; auto g = dir.byDn(gdn); if (!g || g->objectSid.empty() || !g->objectSid.inDomain(domSid)) continue; rids[g->objectSid.rid()] = true; if (auto *outer = g->get("memberOf")) todo.push_back(*outer); } } std::vector<uint32_t> out; for (auto &kv : rids) out.push_back(kv.first); return out; } /* netr_SamInfo2/3/6 (MS-NRPC NETLOGON_VALIDATION_SAM_INFO*) */ void pushValidation(NdrPush &p, uint16_t level, dsdb::Directory &dir, const dsdb::DomainMeta &m, const dsdb::Entry &e, const crypto::Bytes &userKey) { dsdb::Sid domSid = m.sid; if (auto head = dir.byDn(m.baseDn)) if (!head->objectSid.empty()) domSid = head->objectSid; uint32_t primary = uint32_t(dsdb::intValue(e.attrs, "primaryGroupID", dsdb::RID_DOMAIN_USERS)); std::vector<uint32_t> groups = groupRids(dir, e, domSid, primary); int64_t now = dsdb::fileTimeNow(), pwd = dsdb::intValue(e.attrs, "pwdLastSet", 0); const int64_t never = 0x7fffffffffffffffLL; std::string host; { std::lock_guard<std::mutex> l(g_dcMtx); host = g_dcHost; } std::string server = dsdb::upper(host.substr(0, host.find('.'))); std::string sam = e.str("sAMAccountName"), full = e.str("displayName"); std::string script = e.str("scriptPath"), profile = e.str("profilePath"); std::string homeDir = e.str("homeDirectory"), homeDrive = e.str("homeDrive"); uint32_t uac = uint32_t(dsdb::intValue(e.attrs, "userAccountControl", 0x200)); dsdb::Sid uuidSid = dsdb::uuidUserSid(e.objectGuid.value); bool sids = level != 2; p.align(8); for (int64_t t : {now, never, never, pwd, pwd, never}) p.u64(uint64_t(t)); for (auto *str : {&sam, &full, &script, &profile, &homeDir, &homeDrive}) p.unistrHeader(*str); p.u16(0); p.u16(0); /* logon_count, bad_password_count */ p.u32(e.objectSid.empty() ? 0 : e.objectSid.rid()); p.u32(primary); p.u32(uint32_t(groups.size())); p.ref(!groups.empty()); p.u32(sids ? 0x20 : 0); /* user_flags: NETLOGON_EXTRA_SIDS */ p.bytes(userKey.size() == 16 ? userKey : crypto::Bytes(16, 0)); p.unistrHeader(server); p.unistrHeader(m.netbios); p.ref(true); /* domain_sid */ p.bytes(std::vector<uint8_t>(8, 0)); /* LMSessKey */ p.u32((uac & 0x1000) ? 0x80 : (uac & 0x2000) ? 0x100 : 0x10); /* ACB_WSTRUST/SVRTRUST/NORMAL */ p.u32(0); /* sub_auth_status */ p.u64(uint64_t(now)); p.u64(0); /* last_successful/failed_logon */ p.u32(0); p.u32(0); /* failed_logon_count, reserved */ if (sids) { p.u32(1); p.ref(true); } /* sidcount, sids */ std::string upn = e.str("userPrincipalName"); if (upn.empty()) upn = sam + "@" + m.dnsName; if (level == 6) { p.unistrHeader(m.dnsName); p.unistrHeader(upn); for (int i = 0; i < 20; ++i) p.u32(0); } /* deferred */ for (auto *str : {&sam, &full, &script, &profile, &homeDir, &homeDrive}) p.unistrBuffer(*str); if (!groups.empty()) { p.u32(uint32_t(groups.size())); for (uint32_t g : groups) { p.u32(g); p.u32(7); } } p.unistrBuffer(server); p.unistrBuffer(m.netbios); p.sid(domSid); if (sids) { p.u32(1); p.ref(true); p.u32(7); /* netr_SidAttr */ p.sid(uuidSid); } if (level == 6) { p.unistrBuffer(m.dnsName); p.unistrBuffer(upn); } } } void setDcIdentity(const std::string &host, const std::string &address) { Loading Loading @@ -522,6 +722,67 @@ DispatchResult netlogonDispatch(uint16_t opnum, const std::vector<uint8_t> &stub p.u32(0); return DispatchResult{ST_OK, false, 0, p.buf}; } case NETR_LOGONSAMLOGON: case NETR_LOGONSAMLOGONEX: case NETR_LOGONSAMLOGONWITHFLAGS: { NdrPull in(stub); pullStr(in); /* LogonServer */ std::string computer = pullStr(in); if (computer.empty()) computer = schannelComputer; bool withAuth = opnum != NETR_LOGONSAMLOGONEX; Authenticator a; bool haveAuth = false; if (withAuth) { if (in.u32()) { a = pullAuth(in); haveAuth = true; } if (in.u32()) pullAuth(in); /* ReturnAuthenticator (in) */ } LogonRequest lr; bool parsed = pullLogon(in, lr); uint16_t vlevel = in.u16(); uint32_t flags = 0; if (opnum != NETR_LOGONSAMLOGON) flags = in.u32(); NdrPush p; auto finish = [&](uint32_t st) { if (st != ST_OK) { p.u16(vlevel); p.ref(false); } p.u8(1); /* Authoritative */ if (opnum != NETR_LOGONSAMLOGON) p.u32(flags); p.u32(st); return DispatchResult{st, false, 0, p.buf}; }; /* the secure channel: an authenticator, or (Ex) the schannel bind */ Chan ch; crypto::Bytes ret; bool chanOk = false; if (withAuth) { chanOk = haveAuth && stepCredential(computer, a, ret, &ch); p.ref(true); pushAuth(p, chanOk ? ret : crypto::Bytes()); } else if (!schannelComputer.empty()) { std::lock_guard<std::mutex> l(g_mtx); auto it = g_chans.find(dsdb::lower(schannelComputer)); if (it != g_chans.end()) { ch = it->second; chanOk = true; } } if (!chanOk) return finish(ST_ACCESS); if (!parsed) return finish(ST_INVALID_INFO_CLASS); if (vlevel != 2 && vlevel != 3 && vlevel != 6) return finish(ST_INVALID_INFO_CLASS); uint32_t st = ST_NO_SUCH_USER; withDir([&](dsdb::Directory &dir, const dsdb::DomainMeta &m) { std::optional<dsdb::Entry> acct; crypto::Bytes userKey; st = checkLogon(dir, lr, ch.sk, acct, userKey); if (st != ST_OK) return true; /* session keys travel encrypted with the channel key (not * in level 6, which is only used over a sealed channel) */ if (vlevel != 6 && userKey.size() == 16) userKey = crypto::aesCfb8Encrypt(ch.sk, crypto::Bytes(16, 0), userKey); p.u16(vlevel); p.ref(true); pushValidation(p, vlevel, dir, m, *acct, userKey); return true; }); return finish(st); } case DSR_ENUMDOMAINTRUSTS: { /* return this domain as the sole (primary, in-forest) trust so the * client can build its domain context at boot/logon. */ Loading