Commit 916accf7 authored by jan.koester's avatar jan.koester
Browse files

test

parent 348bda03
Loading
Loading
Loading
Loading
Loading
+13 −2
Changes for plugin/krb5/krb5.cpp: 13 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -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;
}

@@ -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);
+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>
@@ -16,6 +17,7 @@ namespace rpc {
namespace {
    enum {
        NETR_SERVERREQCHALLENGE   = 4,
        NETR_LOGONSAMLOGON        = 2,
        NETR_SERVERAUTHENTICATE   = 5,
        NETR_SERVERAUTHENTICATE2  = 15,
        NETR_SERVERAUTHENTICATE3  = 26,
@@ -29,6 +31,7 @@ namespace {
        DSR_GETDCNAMEEX2          = 34,
        NETR_LOGONSAMLOGONEX      = 39,
        DSR_ENUMDOMAINTRUSTS      = 40,   /* DsrEnumerateDomainTrusts    */
        NETR_LOGONSAMLOGONWITHFLAGS = 45,
    };

    std::mutex g_dcMtx;
@@ -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). */
@@ -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) {
@@ -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. */