Loading data/serverconfig.js +7 −0 Changes for data/serverconfig.js: 7 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -16,6 +16,13 @@ var ServerConfig = (function() { { key: "AD/ADMIN_PASSWORD", label: "Administrator password", type: "secret", hint: "set on the Administrator account when a domain is provisioned" } ]}, { title: "DNS", fields: [ { key: "DNS/FORWARDERS", label: "Forwarders", type: "list", hint: "one IP per line; names outside our zones are resolved there" }, { key: "DNS/ALLOW_UPDATE", label: "Dynamic updates (new zones)", type: "select", options: ["nonsecure", "secure", "none"], hint: "nonsecure lets clients register their own records" } ]}, { title: "Service ports", fields: [ { key: "KDC/BIND", label: "Kerberos bind" }, { key: "KDC/PORT", label: "Kerberos port", type: "number" }, Loading plugin/ad/ad.cpp +15 −0 Changes for plugin/ad/ad.cpp: 15 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -542,6 +542,21 @@ ActiveDirectory::Dom *ActiveDirectory::domainByName(const std::string &dnsName) return nullptr; } bool ActiveDirectory::dirWith(const std::string &domain, const std::function<void(dsdb::Directory &)> &fn, std::string &err) { std::lock_guard<std::mutex> lock(_Lock); Dom *d = domainByName(domain); if (!d || !d->dir) { err = "unknown domain '" + domain + "'"; return false; } try { fn(*d->dir); } catch (const std::exception &e) { err = e.what(); return false; } return true; } bool ActiveDirectory::dirList(const std::string &domain, int kind, std::vector<dsdb::Entry> &out, std::string &err) { std::lock_guard<std::mutex> lock(_Lock); 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 @@ -287,6 +287,9 @@ namespace fenrirds { bool dirSetWindowsLogin(const std::string &domain, const uuid::uuid &guid, bool enable, const std::string &clearPassword, std::string &err) override; bool dirWith(const std::string &domain, const std::function<void(dsdb::Directory &)> &fn, 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 plugin/dns/dns.cpp +741 −246 File changed.Preview size limit exceeded, changes collapsed. Show changes plugin/dns/dns.h +76 −35 Changes for plugin/dns/dns.h: 76 added lines, 35 removed lines. Original line number Diff line number Diff line Loading @@ -5,16 +5,24 @@ #pragma once #include <atomic> #include <condition_variable> #include <cstdint> #include <deque> #include <map> #include <memory> #include <mutex> #include <string> #include <thread> #include <vector> #include <sys/socket.h> #include <netplus/socket.h> #include "backend.h" #include "plugin.h" #include "dsdb/directory.h" #include "dsdb/dns.h" #ifdef Windows #define VISIBILITY __declspec(dllexport) Loading @@ -27,12 +35,14 @@ namespace fenrirds { /*************************************************************************** * DNS server * * Serves the service-location records Active Directory clients use to * find the domain controller: the SRV records for LDAP, Kerberos, kpasswd * and the global catalog (in the domain zone and the _msdcs zone), plus * the A records for the domain and the DC host. The record set is * generated from the domains in the directory and the DC's configured * host name and address; dynamic updates come later. * Authoritative for the zones stored in the directories of all domains * (dnsZone/dnsNode, see dsdb/dns.h) plus the service-location records * Active Directory clients use to find the DC (SRV for LDAP, Kerberos, * kpasswd and the GC, A for the domain and the DC host), which are * generated from the configured DC host name and address. Each domain * gets its forward zone on first start. Accepts RFC 2136 dynamic updates * for zones that allow them (unsigned only for now) and forwards queries * for foreign names to the configured upstream resolvers. **************************************************************************/ class VISIBILITY DnsServer : public Plugin { public: Loading @@ -40,52 +50,83 @@ namespace fenrirds { const std::string &bindAddr, int port, const std::string &dcHost, const std::string &dcAddress); const std::string &dcAddress, const std::vector<std::string> &forwarders, uint8_t defaultAllowUpdate); ~DnsServer() override; const char *name() const override { return "dns"; } void init() override; /* bind sockets (while root) */ void run() override; /* serve (blocking) */ void stop() override { _Running = false; } void stop() override; private: /* DNS resource record kinds we answer */ enum RrType : uint16_t { RR_A = 1, RR_NS = 2, RR_CNAME= 5, RR_SOA = 6, RR_AAAA = 28, RR_SRV = 33, RR_ANY = 255, /* one DNS message in, the response out (empty = no answer). For UDP * the response is truncated to what the client accepts; a forwarded * query is answered asynchronously through `deferred` if given. */ struct Client { bool tcp = false; sockaddr_storage addr{}; socklen_t addrLen = 0; }; std::vector<uint8_t> handleMessage(const uint8_t *data, size_t len, const Client &cl, bool &deferred); struct Record { uint16_t type; /* A: address in addr; SRV: prio/weight/port + target; NS/CNAME: target */ std::string addr; /* dotted IPv4 for A */ uint16_t prio = 0, weight = 0, port = 0; std::string target; /* FQDN for SRV/NS/CNAME/SOA mname */ private: struct Zone { std::string name; /* lower case */ uint8_t allowUpdate = dsdb::dns::UPDATE_NONE; bool stored = false; /* has a dnsZone object */ uuid::uuid domainId; std::string domainName; }; struct Snapshot { /* lower-case owner FQDN -> records */ std::multimap<std::string, dsdb::dns::Rr> rrs; std::vector<Zone> zones; }; std::shared_ptr<const Snapshot> snapshot(); void rebuild(); dsdb::Directory *directory(const uuid::uuid &did, const std::string &name); const Zone *findZone(const Snapshot &s, const std::string &name) const; void buildZone(); /* (re)generate the record table */ std::vector<uint8_t> handleQuery(const uint8_t *data, size_t len); std::vector<uint8_t> answerSection(const std::string &qname, uint16_t qtype, int &answerCount, bool &nameExists); std::vector<uint8_t> additionalSection(const std::string &qname, uint16_t qtype, int &addCount); void emitRecord(std::vector<uint8_t> &out, const std::string &owner, const Record &r, int &count); std::vector<uint8_t> handleQuery(const uint8_t *data, size_t len, size_t qend, const std::string &qname, uint16_t qtype, size_t udpLimit); std::vector<uint8_t> handleUpdate(const uint8_t *data, size_t len); /* forwarding */ void forwardWorker(); bool forwardQuery(const std::vector<uint8_t> &q, bool tcp, std::vector<uint8_t> &resp); void sendUdp(const std::vector<uint8_t> &resp, const Client &cl); AuthBackend &_AdminBackend; std::string _BindAddr; int _Port; std::string _DcHost; /* e.g. dc1.example.com */ std::string _DcAddress; /* IPv4 */ std::vector<std::string> _Forwarders; uint8_t _DefaultAllowUpdate; std::mutex _SnapMtx; std::shared_ptr<const Snapshot> _Snap; std::string _SnapKey; /* backend revisions it was built from */ int64_t _SnapChecked = 0; bool _Dirty = true; /* lower-case owner name -> records */ std::multimap<std::string, Record> _Zone; std::vector<std::string> _ZoneRoots; /* domains we are authoritative for */ std::mutex _DirMtx; std::map<std::string, std::unique_ptr<dsdb::Directory>> _Dirs; /* domain id -> view */ std::map<std::string, bool> _Ensured; /* forward zone created */ struct Job { std::vector<uint8_t> query; Client client; }; std::mutex _JobMtx; std::condition_variable _JobCv; std::deque<Job> _Jobs; std::vector<std::thread> _Workers; std::mutex _SendMtx; netplus::udp *_Udp = nullptr; netplus::tcp *_Tcp = nullptr; Loading Loading
data/serverconfig.js +7 −0 Changes for data/serverconfig.js: 7 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -16,6 +16,13 @@ var ServerConfig = (function() { { key: "AD/ADMIN_PASSWORD", label: "Administrator password", type: "secret", hint: "set on the Administrator account when a domain is provisioned" } ]}, { title: "DNS", fields: [ { key: "DNS/FORWARDERS", label: "Forwarders", type: "list", hint: "one IP per line; names outside our zones are resolved there" }, { key: "DNS/ALLOW_UPDATE", label: "Dynamic updates (new zones)", type: "select", options: ["nonsecure", "secure", "none"], hint: "nonsecure lets clients register their own records" } ]}, { title: "Service ports", fields: [ { key: "KDC/BIND", label: "Kerberos bind" }, { key: "KDC/PORT", label: "Kerberos port", type: "number" }, Loading
plugin/ad/ad.cpp +15 −0 Changes for plugin/ad/ad.cpp: 15 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -542,6 +542,21 @@ ActiveDirectory::Dom *ActiveDirectory::domainByName(const std::string &dnsName) return nullptr; } bool ActiveDirectory::dirWith(const std::string &domain, const std::function<void(dsdb::Directory &)> &fn, std::string &err) { std::lock_guard<std::mutex> lock(_Lock); Dom *d = domainByName(domain); if (!d || !d->dir) { err = "unknown domain '" + domain + "'"; return false; } try { fn(*d->dir); } catch (const std::exception &e) { err = e.what(); return false; } return true; } bool ActiveDirectory::dirList(const std::string &domain, int kind, std::vector<dsdb::Entry> &out, std::string &err) { std::lock_guard<std::mutex> lock(_Lock); 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 @@ -287,6 +287,9 @@ namespace fenrirds { bool dirSetWindowsLogin(const std::string &domain, const uuid::uuid &guid, bool enable, const std::string &clearPassword, std::string &err) override; bool dirWith(const std::string &domain, const std::function<void(dsdb::Directory &)> &fn, 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
plugin/dns/dns.cpp +741 −246 File changed.Preview size limit exceeded, changes collapsed. Show changes
plugin/dns/dns.h +76 −35 Changes for plugin/dns/dns.h: 76 added lines, 35 removed lines. Original line number Diff line number Diff line Loading @@ -5,16 +5,24 @@ #pragma once #include <atomic> #include <condition_variable> #include <cstdint> #include <deque> #include <map> #include <memory> #include <mutex> #include <string> #include <thread> #include <vector> #include <sys/socket.h> #include <netplus/socket.h> #include "backend.h" #include "plugin.h" #include "dsdb/directory.h" #include "dsdb/dns.h" #ifdef Windows #define VISIBILITY __declspec(dllexport) Loading @@ -27,12 +35,14 @@ namespace fenrirds { /*************************************************************************** * DNS server * * Serves the service-location records Active Directory clients use to * find the domain controller: the SRV records for LDAP, Kerberos, kpasswd * and the global catalog (in the domain zone and the _msdcs zone), plus * the A records for the domain and the DC host. The record set is * generated from the domains in the directory and the DC's configured * host name and address; dynamic updates come later. * Authoritative for the zones stored in the directories of all domains * (dnsZone/dnsNode, see dsdb/dns.h) plus the service-location records * Active Directory clients use to find the DC (SRV for LDAP, Kerberos, * kpasswd and the GC, A for the domain and the DC host), which are * generated from the configured DC host name and address. Each domain * gets its forward zone on first start. Accepts RFC 2136 dynamic updates * for zones that allow them (unsigned only for now) and forwards queries * for foreign names to the configured upstream resolvers. **************************************************************************/ class VISIBILITY DnsServer : public Plugin { public: Loading @@ -40,52 +50,83 @@ namespace fenrirds { const std::string &bindAddr, int port, const std::string &dcHost, const std::string &dcAddress); const std::string &dcAddress, const std::vector<std::string> &forwarders, uint8_t defaultAllowUpdate); ~DnsServer() override; const char *name() const override { return "dns"; } void init() override; /* bind sockets (while root) */ void run() override; /* serve (blocking) */ void stop() override { _Running = false; } void stop() override; private: /* DNS resource record kinds we answer */ enum RrType : uint16_t { RR_A = 1, RR_NS = 2, RR_CNAME= 5, RR_SOA = 6, RR_AAAA = 28, RR_SRV = 33, RR_ANY = 255, /* one DNS message in, the response out (empty = no answer). For UDP * the response is truncated to what the client accepts; a forwarded * query is answered asynchronously through `deferred` if given. */ struct Client { bool tcp = false; sockaddr_storage addr{}; socklen_t addrLen = 0; }; std::vector<uint8_t> handleMessage(const uint8_t *data, size_t len, const Client &cl, bool &deferred); struct Record { uint16_t type; /* A: address in addr; SRV: prio/weight/port + target; NS/CNAME: target */ std::string addr; /* dotted IPv4 for A */ uint16_t prio = 0, weight = 0, port = 0; std::string target; /* FQDN for SRV/NS/CNAME/SOA mname */ private: struct Zone { std::string name; /* lower case */ uint8_t allowUpdate = dsdb::dns::UPDATE_NONE; bool stored = false; /* has a dnsZone object */ uuid::uuid domainId; std::string domainName; }; struct Snapshot { /* lower-case owner FQDN -> records */ std::multimap<std::string, dsdb::dns::Rr> rrs; std::vector<Zone> zones; }; std::shared_ptr<const Snapshot> snapshot(); void rebuild(); dsdb::Directory *directory(const uuid::uuid &did, const std::string &name); const Zone *findZone(const Snapshot &s, const std::string &name) const; void buildZone(); /* (re)generate the record table */ std::vector<uint8_t> handleQuery(const uint8_t *data, size_t len); std::vector<uint8_t> answerSection(const std::string &qname, uint16_t qtype, int &answerCount, bool &nameExists); std::vector<uint8_t> additionalSection(const std::string &qname, uint16_t qtype, int &addCount); void emitRecord(std::vector<uint8_t> &out, const std::string &owner, const Record &r, int &count); std::vector<uint8_t> handleQuery(const uint8_t *data, size_t len, size_t qend, const std::string &qname, uint16_t qtype, size_t udpLimit); std::vector<uint8_t> handleUpdate(const uint8_t *data, size_t len); /* forwarding */ void forwardWorker(); bool forwardQuery(const std::vector<uint8_t> &q, bool tcp, std::vector<uint8_t> &resp); void sendUdp(const std::vector<uint8_t> &resp, const Client &cl); AuthBackend &_AdminBackend; std::string _BindAddr; int _Port; std::string _DcHost; /* e.g. dc1.example.com */ std::string _DcAddress; /* IPv4 */ std::vector<std::string> _Forwarders; uint8_t _DefaultAllowUpdate; std::mutex _SnapMtx; std::shared_ptr<const Snapshot> _Snap; std::string _SnapKey; /* backend revisions it was built from */ int64_t _SnapChecked = 0; bool _Dirty = true; /* lower-case owner name -> records */ std::multimap<std::string, Record> _Zone; std::vector<std::string> _ZoneRoots; /* domains we are authoritative for */ std::mutex _DirMtx; std::map<std::string, std::unique_ptr<dsdb::Directory>> _Dirs; /* domain id -> view */ std::map<std::string, bool> _Ensured; /* forward zone created */ struct Job { std::vector<uint8_t> query; Client client; }; std::mutex _JobMtx; std::condition_variable _JobCv; std::deque<Job> _Jobs; std::vector<std::thread> _Workers; std::mutex _SendMtx; netplus::udp *_Udp = nullptr; netplus::tcp *_Tcp = nullptr; Loading