Commit 12e2dc4c authored by jan.koester's avatar jan.koester
Browse files

test

parent f8cc78c0
Loading
Loading
Loading
Loading
Loading
+169 −4
Changes for plugin/ad/ad.cpp: 169 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@
#include <algorithm>
#include <sstream>
#include <memory>
#include <functional>
#include <optional>

#include <netplus/socket.h>
@@ -38,6 +39,9 @@

#include "ad.h"
#include "crypto/gss.h"

#include <sys/socket.h>
#include <netinet/in.h>
#include "fenrirds.h"
#include "domains.h"
#include "backend.h"
@@ -348,10 +352,11 @@ namespace {
ActiveDirectory::ActiveDirectory(AuthBackend &adminBackend,
                                 const std::string &bindAddr,
                                 int port, int maxconn,
                                 const std::string &adminPassword)
                                 const std::string &adminPassword,
                                 const std::string &dcHost)
    : _AdminBackend(adminBackend),
      _BindAddr(bindAddr), _Port(port), _MaxConn(maxconn),
      _AdminPassword(adminPassword),
      _AdminPassword(adminPassword), _DcHost(dcHost),
      _Event(nullptr)
{
    DomainRegistry::instance().setAdminBackend(&adminBackend);
@@ -359,8 +364,11 @@ ActiveDirectory::ActiveDirectory(AuthBackend &adminBackend,

    netplus::tcp *ssock = new netplus::tcp(bindAddr.c_str(), port, maxconn, 0);
    _ServerSockets.push_back(ssock);
    /* CLDAP (connectionless LDAP) netlogon ping on the same port, UDP */
    _Cldap = new netplus::udp(bindAddr.c_str(), port, maxconn, -1);

    std::cerr << "AD/LDAP plugin listening on " << bindAddr << ":" << port << std::endl;
    std::cerr << "AD/LDAP plugin listening on " << bindAddr << ":" << port
              << " (+CLDAP udp)" << std::endl;
}

void ActiveDirectory::init() {
@@ -369,15 +377,39 @@ void ActiveDirectory::init() {
        s->setNonBlock();
        s->listen();
    }
    _Cldap->bind();
}

ActiveDirectory::~ActiveDirectory() {
    _Running = false;
    if (_CldapThread.joinable())
        _CldapThread.join();
    delete _Cldap;
    delete static_cast<AdEvent*>(_Event);
    for (auto *s : _ServerSockets)
        delete s;
}

void ActiveDirectory::run() {
    _Running = true;
    _CldapThread = std::thread([this]() {
        while (_Running) {
            std::vector<std::vector<uint8_t>> dgrams;
            std::vector<sockaddr_storage> addrs;
            size_t n = 0;
            try {
                n = _Cldap->recvBatchAddr(dgrams, addrs, 16);
            } catch (...) { continue; }
            for (size_t i = 0; i < n; ++i) {
                auto resp = handleCldap(dgrams[i].data(), dgrams[i].size());
                if (resp.empty())
                    continue;
                socklen_t sl = addrs[i].ss_family == AF_INET6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in);
                std::vector<std::pair<const uint8_t*, size_t>> out = {{resp.data(), resp.size()}};
                try { _Cldap->sendBatch(out, &addrs[i], sl); } catch (...) {}
            }
        }
    });
    auto *ev = new AdEvent(_ServerSockets, this);
    _Event = ev;
    ev->runEventloop(0);
@@ -860,6 +892,137 @@ LdapEntry ActiveDirectory::renderEntry(const dsdb::Entry &ent,
/******************************************************************************
 * RootDSE
 *****************************************************************************/
/******************************************************************************
 * CLDAP netlogon ping (MS-ADTS 6.3.3 / 6.3.5): a connectionless LDAP search
 * with an empty base and a filter naming the domain and NtVer. We answer with
 * a searchResultEntry carrying the Netlogon attribute (a
 * NETLOGON_SAM_LOGON_RESPONSE_EX) so clients can pick this DC.
 *****************************************************************************/
namespace {
    void nlName(std::vector<uint8_t> &o, const std::string &name) {
        size_t i = 0;
        while (i < name.size()) {
            size_t dot = name.find('.', i);
            if (dot == std::string::npos) dot = name.size();
            size_t l = dot - i;
            if (l == 0) break;
            o.push_back(uint8_t(l));
            for (size_t k = i; k < dot; ++k) o.push_back(uint8_t(name[k]));
            i = dot + 1;
        }
        o.push_back(0);
    }
    void nlU16(std::vector<uint8_t> &o, uint16_t v) { o.push_back(v & 0xff); o.push_back(v >> 8); }
    void nlU32(std::vector<uint8_t> &o, uint32_t v) { for (int i = 0; i < 4; ++i) o.push_back((v >> (8 * i)) & 0xff); }
}

std::vector<uint8_t> ActiveDirectory::buildNetlogonResponse(const Dom &d, uint32_t ntVersion,
                                                            const std::string &user) {
    std::vector<uint8_t> b;
    std::string host = _DcHost.empty() ? ("dc." + d.meta.dnsName) : _DcHost;
    std::string comp = host.substr(0, host.find('.'));
    std::string site = "Default-First-Site-Name";

    uint32_t flags = 0x1 | 0x4 | 0x8 | 0x10 | 0x20 | 0x40 | 0x80 | 0x100 | 0x200
                   | 0x20000000u | 0x40000000u | 0x80000000u;  /* PDC/GC/LDAP/DS/KDC/TIMESERV/CLOSEST/WRITABLE/GOODTIME + DNS_{CONTROLLER,DOMAIN,FOREST} */

    nlU16(b, 23);          /* Opcode = LOGON_SAM_LOGON_RESPONSE_EX */
    nlU16(b, 0);           /* Sbz */
    nlU32(b, flags);
    /* DomainGuid: the domain head objectGUID */
    uint8_t guid[16] = {0};
    if (auto head = d.dir->byDn(d.meta.baseDn))
        std::memcpy(guid, head->objectGuid.value, 16);
    b.insert(b.end(), guid, guid + 16);
    nlName(b, d.meta.dnsName);             /* DnsForestName (= domain, single-domain forest) */
    nlName(b, d.meta.dnsName);             /* DnsDomainName */
    nlName(b, host);                       /* DnsHostName */
    nlName(b, d.meta.netbios);             /* NetbiosDomainName */
    nlName(b, dsdb::upper(comp));          /* NetbiosComputerName */
    nlName(b, user);                       /* UserName (echo / empty) */
    nlName(b, site);                       /* DcSiteName */
    nlName(b, site);                       /* ClientSiteName */
    nlU32(b, ntVersion ? ntVersion : 0x00000005);
    nlU16(b, 0xffff);                      /* LmNtToken */
    nlU16(b, 0xffff);                      /* Lm20Token */
    return b;
}

std::vector<uint8_t> ActiveDirectory::handleCldap(const uint8_t *data, size_t len) {
    /* parse LDAPMessage { msgid, searchRequest } */
    size_t off = 0;
    uint8_t tag;
    const uint8_t *c;
    size_t cl;
    if (!LdapBer::parseTLV(data, len, off, tag, c, cl) || tag != LdapBer::TAG_SEQUENCE)
        return {};
    const uint8_t *msg = c;
    size_t mlen = cl;
    size_t mo = 0;
    if (!LdapBer::parseTLV(msg, mlen, mo, tag, c, cl))
        return {};
    int32_t msgId = 0;
    LdapBer::parseInteger(c, cl, msgId);
    if (!LdapBer::parseTLV(msg, mlen, mo, tag, c, cl))
        return {};
    if ((tag & 0x1F) != LDAP_SEARCH_REQUEST)
        return {};

    /* search fields: base, scope, deref, sizelimit, timelimit, typesOnly, filter, attrs */
    const uint8_t *sc = c;
    size_t sl = cl, so = 0;
    const uint8_t *f;
    size_t fl;
    uint8_t st;
    std::string base;
    if (!LdapBer::parseTLV(sc, sl, so, st, f, fl)) return {};
    LdapBer::parseOctetString(f, fl, base);
    for (int i = 0; i < 5; ++i)             /* skip scope..typesOnly */
        if (!LdapBer::parseTLV(sc, sl, so, st, f, fl)) return {};
    size_t filtOff = so;
    if (!LdapBer::parseTLV(sc, sl, so, st, f, fl)) return {};
    dsdb::Filter filter;
    parseFilter(sc + filtOff, so - filtOff, filter);

    /* walk the filter for DnsDomain / NtVer / User */
    std::string dnsDomain, user;
    uint32_t ntVer = 0;
    std::function<void(const dsdb::Filter &)> walk = [&](const dsdb::Filter &ff) {
        for (auto &ch : ff.children) walk(ch);
        if (ff.type == dsdb::Filter::Equal) {
            if (dsdb::iequals(ff.attr, "DnsDomain")) dnsDomain = ff.value;
            else if (dsdb::iequals(ff.attr, "User")) user = ff.value;
            else if (dsdb::iequals(ff.attr, "NtVer") && ff.value.size() >= 4)
                ntVer = uint8_t(ff.value[0]) | uint8_t(ff.value[1]) << 8
                      | uint8_t(ff.value[2]) << 16 | uint8_t(ff.value[3]) << 24;
        }
    };
    walk(filter);

    /* pick the domain: by DnsDomain, else the first */
    std::lock_guard<std::mutex> lock(_Lock);
    Dom *dom = nullptr;
    for (auto &d : _Domains) {
        if (dnsDomain.empty() || dsdb::iequals(d.meta.dnsName, dnsDomain)) { dom = &d; break; }
    }
    if (!dom)
        return {};

    auto blob = buildNetlogonResponse(*dom, ntVer, user);

    /* searchResultEntry { objectName "", attributes { { "Netlogon", { blob } } } } */
    LdapEntry e;
    e.dn = "";
    LdapAttribute a;
    a.type = "Netlogon";
    a.values.push_back(std::string(blob.begin(), blob.end()));
    e.attributes.push_back(std::move(a));
    auto entry = buildSearchResultEntry(msgId, e);
    auto done = buildSearchResultDone(msgId, LDAP_SUCCESS);
    entry.insert(entry.end(), done.begin(), done.end());
    return entry;
}

LdapEntry ActiveDirectory::buildRootDSE() {
    LdapEntry e;
    e.dn = "";
@@ -1393,6 +1556,7 @@ fenrirds::Plugin *fenrirds_plugin_create(fenrirds::AuthBackend &backend,
    int port = 389;
    int maxconn = 128;
    std::string adminPassword;
    std::string dcHost;

    if (configJson) {
        json_object *jobj = json_tokener_parse(configJson);
@@ -1406,11 +1570,12 @@ fenrirds::Plugin *fenrirds_plugin_create(fenrirds::AuthBackend &backend,
            if (jport) port     = json_object_get_int(jport);
            if (jmax)  maxconn  = json_object_get_int(jmax);
            if (jpw)   adminPassword = json_object_get_string(jpw);
            if (auto *jh = json_object_object_get(jobj, "dns_hostname")) dcHost = json_object_get_string(jh);
            json_object_put(jobj);
        }
    }

    return new fenrirds::ActiveDirectory(backend, bindAddr, port, maxconn, adminPassword);
    return new fenrirds::ActiveDirectory(backend, bindAddr, port, maxconn, adminPassword, dcHost);
}

void fenrirds_plugin_destroy(fenrirds::Plugin *p) {
+15 −1
Changes for plugin/ad/ad.h: 15 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -31,6 +31,8 @@
#include <vector>
#include <map>
#include <mutex>
#include <thread>
#include <atomic>
#include <memory>
#include <cstdint>

@@ -243,7 +245,8 @@ namespace fenrirds {
        ActiveDirectory(AuthBackend &adminBackend,
                        const std::string &bindAddr = "0.0.0.0",
                        int port = 389, int maxconn = 128,
                        const std::string &adminPassword = "");
                        const std::string &adminPassword = "",
                        const std::string &dcHost = "");
        ~ActiveDirectory() override;

        /* Plugin interface */
@@ -279,6 +282,9 @@ namespace fenrirds {
                                                int32_t msgId, LdapSession &session);
        std::vector<uint8_t> handleUnbindRequest(int32_t msgId, LdapSession &session);

        /* ---- CLDAP netlogon ping (UDP 389, DC location) ---- */
        std::vector<uint8_t> handleCldap(const uint8_t *data, size_t len);

        /* ---- LDAP Response Builders ---- */
        std::vector<uint8_t> buildLdapMessage(int32_t msgId, uint8_t appTag,
                                              const std::vector<uint8_t> &protocolOp);
@@ -296,6 +302,9 @@ namespace fenrirds {
            dsdb::DomainMeta           meta;
            std::unique_ptr<dsdb::Directory> dir;
        };
        /* CLDAP netlogon response (needs Dom) */
        std::vector<uint8_t> buildNetlogonResponse(const Dom &d, uint32_t ntVersion,
                                                   const std::string &user);
        /* (re)load the domains from the registry, provisioning each */
        void loadDomains();
        /* the domain whose base DN contains dn (longest match), or nullptr */
@@ -319,6 +328,7 @@ namespace fenrirds {
        int                                    _Port;
        int                                    _MaxConn;
        std::string                            _AdminPassword;
        std::string                            _DcHost;    /* FQDN for CLDAP netlogon */

        std::vector<Dom>                       _Domains;

@@ -328,6 +338,10 @@ namespace fenrirds {

        std::vector<netplus::socket*>          _ServerSockets;
        void                                  *_Event;

        netplus::udp                          *_Cldap = nullptr;
        std::thread                            _CldapThread;
        std::atomic<bool>                      _Running{false};
    };

}; /* namespace fenrirds */