Loading CMakeLists.txt +1 −1 Changes for CMakeLists.txt: 1 added line, 1 removed line. Original line number Diff line number Diff line cmake_minimum_required(VERSION 3.16) project(adconnect VERSION 0.2.0 LANGUAGES CXX) project(adconnect VERSION 0.2.1 LANGUAGES CXX) # adconnect: the users and groups of an Active Directory domain for # systempp's user lookups (getpwnam(), getgrouplist(), ... -- see Loading src/daemon.cpp +29 −2 Changes for src/daemon.cpp: 29 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -292,8 +292,10 @@ void serve(Server &s, std::unique_ptr<netplus::socket> c) { void usage() { std::cerr << "usage: adconnectd [-c config] [-n | -t | -q \"REQUEST\"]\n" " adconnectd -j DOMAIN -u ADMIN [-s SECRETDIR] (the password on stdin)\n" " adconnectd [-c config] -l DOMAIN -u ADMIN (the password on stdin)\n" " -j join DOMAIN: a computer account for this system, made with the\n" " administrator's password; prints the settings for adconnect.yaml\n" " -l leave DOMAIN: its computer account deleted (an administrator's password)\n" " -n only read the configuration (no network), then exit\n" " -t connect and bind to a domain controller of each domain, then exit\n" " -q answer one request (\"PWNAM jan\", \"GROUPS jan\", ...) and exit\n"; Loading @@ -307,12 +309,13 @@ int main(int argc, char **argv) { std::string configPath = ADCONNECT_CONFIG, query; bool check = false, parseOnly = false; std::string joinDomainName, joinAdmin, secretDir = "/etc/adconnect"; std::string joinDomainName, joinAdmin, leaveDomainName, secretDir = "/etc/adconnect"; int opt; while ((opt = getopt(argc, argv, "c:ntq:j:u:s:h")) != -1) { while ((opt = getopt(argc, argv, "c:ntq:j:l:u:s:h")) != -1) { switch (opt) { case 'c': configPath = optarg; break; case 'j': joinDomainName = optarg; break; case 'l': leaveDomainName = optarg; break; case 'u': joinAdmin = optarg; break; case 's': secretDir = optarg; break; case 'n': parseOnly = true; break; Loading Loading @@ -355,6 +358,30 @@ int main(int argc, char **argv) { for (const Config &c : configs) std::cout << "adconnectd: " << c.domain << " (" << c.base << ")" << std::endl; return 0; } if (!leaveDomainName.empty()) { if (joinAdmin.empty()) { usage(); return 2; } const Config *d = nullptr; for (const Config &c : configs) if (c.domain == leaveDomainName) d = &c; if (!d) { std::cerr << "adconnectd: no domain " << leaveDomainName << " in " << configPath << std::endl; return 1; } std::string password; std::getline(std::cin, password); while (!password.empty() && (password.back() == '\r' || password.back() == '\n')) password.pop_back(); try { bool deleted = leaveDomain(*d, d->krbPrincipal, joinAdmin, password); std::cout << "deleted=" << (deleted ? "yes" : "no") << std::endl; } catch (const std::exception &e) { std::cerr << "adconnectd: leaving " << leaveDomainName << ": " << e.what() << std::endl; return 1; } return 0; } const Config &cfg = configs.front(); Server s{ cfg, {} }; for (const Config &c : configs) s.domains.push_back(std::make_unique<Domain>(c)); Loading src/directory.cpp +5 −0 Changes for src/directory.cpp: 5 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -136,6 +136,11 @@ bool LdapSource::ownKey(int etype, krb5::Key &key) { return true; } void LdapSource::remove(const std::string &dn, bool treeDelete) { ensure(); _conn.remove(dn, treeDelete); } void LdapSource::add(const std::string &dn, const ldap::Connection::Attributes &attrs) { ensure(); _conn.add(dn, attrs); Loading src/directory.h +1 −0 Changes for src/directory.h: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -81,6 +81,7 @@ public: void add(const std::string &dn, const ldap::Connection::Attributes &attrs); void modify(const std::string &dn, const std::vector<std::pair<ldap::Connection::ModOp, ldap::Connection::Attributes::value_type>> &changes); void remove(const std::string &dn, bool treeDelete); private: void ensure(); Loading src/join.cpp +44 −7 Changes for src/join.cpp: 44 added lines, 7 removed lines. Original line number Diff line number Diff line Loading @@ -204,14 +204,29 @@ JoinResult joinWith(const Config &admin, const std::string &hostname, const std: return r; } JoinResult joinDomain(const std::string &domainIn, const std::string &adminIn, const std::string &adminPassword, const std::string &secretDir, int timeoutSec) { std::string domain = lower(domainIn); while (!domain.empty() && domain.back() == '.') domain.pop_back(); if (domain.empty()) throw std::runtime_error("no domain"); bool leaveWith(const Config &admin, const std::string &account) { LdapSource src(admin); src.check(); std::string dn; src.search(admin.base, ldap::Subtree, "(&(objectClass=computer)(sAMAccountName=" + ldap::escape(account) + "))", { "sAMAccountName" }, [&](const ldap::Entry &e) { dn = e.dn; }, 0); if (dn.empty()) return false; try { src.remove(dn, false); } catch (const ldap::Error &e) { if (e.code() != 66) writeFailed(e, "deleting " + dn); /* notAllowedOnNonLeaf: it has entries below */ try { src.remove(dn, true); } catch (const ldap::Error &e2) { writeFailed(e2, "deleting " + dn + " with what's below it"); } } return true; } /* DOMAIN\admin, admin@REALM or admin */ std::string admin = adminIn, realm = upper(domain); /* DOMAIN\admin, admin@REALM or admin: the account and its realm */ static void adminPrincipal(const std::string &in, std::string &admin, std::string &realm) { admin = in; size_t bs = admin.find('\\'); if (bs != std::string::npos) admin.erase(0, bs + 1); size_t at = admin.find('@'); Loading @@ -220,6 +235,28 @@ JoinResult joinDomain(const std::string &domainIn, const std::string &adminIn, c admin.erase(at); } if (admin.empty()) throw std::runtime_error("no administrator account"); } bool leaveDomain(const Config &domain, const std::string &account, const std::string &adminIn, const std::string &adminPassword) { if (account.empty()) throw std::runtime_error(domain.domain + ": no computer account configured (kerberos_principal)"); Config c = domain; c.auth = Auth::Kerberos; c.krbRealm = domain.auth == Auth::Kerberos && !domain.krbRealm.empty() ? domain.krbRealm : upper(domain.domain); adminPrincipal(adminIn, c.krbPrincipal, c.krbRealm); c.bindPassword = adminPassword; c.keytab.clear(); if (c.kdcs.empty()) c.kdcs = c.servers; return leaveWith(c, account); } JoinResult joinDomain(const std::string &domainIn, const std::string &adminIn, const std::string &adminPassword, const std::string &secretDir, int timeoutSec) { std::string domain = lower(domainIn); while (!domain.empty() && domain.back() == '.') domain.pop_back(); if (domain.empty()) throw std::runtime_error("no domain"); std::string admin, realm = upper(domain); adminPrincipal(adminIn, admin, realm); Config c; c.domain = domain; Loading Loading
CMakeLists.txt +1 −1 Changes for CMakeLists.txt: 1 added line, 1 removed line. Original line number Diff line number Diff line cmake_minimum_required(VERSION 3.16) project(adconnect VERSION 0.2.0 LANGUAGES CXX) project(adconnect VERSION 0.2.1 LANGUAGES CXX) # adconnect: the users and groups of an Active Directory domain for # systempp's user lookups (getpwnam(), getgrouplist(), ... -- see Loading
src/daemon.cpp +29 −2 Changes for src/daemon.cpp: 29 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -292,8 +292,10 @@ void serve(Server &s, std::unique_ptr<netplus::socket> c) { void usage() { std::cerr << "usage: adconnectd [-c config] [-n | -t | -q \"REQUEST\"]\n" " adconnectd -j DOMAIN -u ADMIN [-s SECRETDIR] (the password on stdin)\n" " adconnectd [-c config] -l DOMAIN -u ADMIN (the password on stdin)\n" " -j join DOMAIN: a computer account for this system, made with the\n" " administrator's password; prints the settings for adconnect.yaml\n" " -l leave DOMAIN: its computer account deleted (an administrator's password)\n" " -n only read the configuration (no network), then exit\n" " -t connect and bind to a domain controller of each domain, then exit\n" " -q answer one request (\"PWNAM jan\", \"GROUPS jan\", ...) and exit\n"; Loading @@ -307,12 +309,13 @@ int main(int argc, char **argv) { std::string configPath = ADCONNECT_CONFIG, query; bool check = false, parseOnly = false; std::string joinDomainName, joinAdmin, secretDir = "/etc/adconnect"; std::string joinDomainName, joinAdmin, leaveDomainName, secretDir = "/etc/adconnect"; int opt; while ((opt = getopt(argc, argv, "c:ntq:j:u:s:h")) != -1) { while ((opt = getopt(argc, argv, "c:ntq:j:l:u:s:h")) != -1) { switch (opt) { case 'c': configPath = optarg; break; case 'j': joinDomainName = optarg; break; case 'l': leaveDomainName = optarg; break; case 'u': joinAdmin = optarg; break; case 's': secretDir = optarg; break; case 'n': parseOnly = true; break; Loading Loading @@ -355,6 +358,30 @@ int main(int argc, char **argv) { for (const Config &c : configs) std::cout << "adconnectd: " << c.domain << " (" << c.base << ")" << std::endl; return 0; } if (!leaveDomainName.empty()) { if (joinAdmin.empty()) { usage(); return 2; } const Config *d = nullptr; for (const Config &c : configs) if (c.domain == leaveDomainName) d = &c; if (!d) { std::cerr << "adconnectd: no domain " << leaveDomainName << " in " << configPath << std::endl; return 1; } std::string password; std::getline(std::cin, password); while (!password.empty() && (password.back() == '\r' || password.back() == '\n')) password.pop_back(); try { bool deleted = leaveDomain(*d, d->krbPrincipal, joinAdmin, password); std::cout << "deleted=" << (deleted ? "yes" : "no") << std::endl; } catch (const std::exception &e) { std::cerr << "adconnectd: leaving " << leaveDomainName << ": " << e.what() << std::endl; return 1; } return 0; } const Config &cfg = configs.front(); Server s{ cfg, {} }; for (const Config &c : configs) s.domains.push_back(std::make_unique<Domain>(c)); Loading
src/directory.cpp +5 −0 Changes for src/directory.cpp: 5 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -136,6 +136,11 @@ bool LdapSource::ownKey(int etype, krb5::Key &key) { return true; } void LdapSource::remove(const std::string &dn, bool treeDelete) { ensure(); _conn.remove(dn, treeDelete); } void LdapSource::add(const std::string &dn, const ldap::Connection::Attributes &attrs) { ensure(); _conn.add(dn, attrs); Loading
src/directory.h +1 −0 Changes for src/directory.h: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -81,6 +81,7 @@ public: void add(const std::string &dn, const ldap::Connection::Attributes &attrs); void modify(const std::string &dn, const std::vector<std::pair<ldap::Connection::ModOp, ldap::Connection::Attributes::value_type>> &changes); void remove(const std::string &dn, bool treeDelete); private: void ensure(); Loading
src/join.cpp +44 −7 Changes for src/join.cpp: 44 added lines, 7 removed lines. Original line number Diff line number Diff line Loading @@ -204,14 +204,29 @@ JoinResult joinWith(const Config &admin, const std::string &hostname, const std: return r; } JoinResult joinDomain(const std::string &domainIn, const std::string &adminIn, const std::string &adminPassword, const std::string &secretDir, int timeoutSec) { std::string domain = lower(domainIn); while (!domain.empty() && domain.back() == '.') domain.pop_back(); if (domain.empty()) throw std::runtime_error("no domain"); bool leaveWith(const Config &admin, const std::string &account) { LdapSource src(admin); src.check(); std::string dn; src.search(admin.base, ldap::Subtree, "(&(objectClass=computer)(sAMAccountName=" + ldap::escape(account) + "))", { "sAMAccountName" }, [&](const ldap::Entry &e) { dn = e.dn; }, 0); if (dn.empty()) return false; try { src.remove(dn, false); } catch (const ldap::Error &e) { if (e.code() != 66) writeFailed(e, "deleting " + dn); /* notAllowedOnNonLeaf: it has entries below */ try { src.remove(dn, true); } catch (const ldap::Error &e2) { writeFailed(e2, "deleting " + dn + " with what's below it"); } } return true; } /* DOMAIN\admin, admin@REALM or admin */ std::string admin = adminIn, realm = upper(domain); /* DOMAIN\admin, admin@REALM or admin: the account and its realm */ static void adminPrincipal(const std::string &in, std::string &admin, std::string &realm) { admin = in; size_t bs = admin.find('\\'); if (bs != std::string::npos) admin.erase(0, bs + 1); size_t at = admin.find('@'); Loading @@ -220,6 +235,28 @@ JoinResult joinDomain(const std::string &domainIn, const std::string &adminIn, c admin.erase(at); } if (admin.empty()) throw std::runtime_error("no administrator account"); } bool leaveDomain(const Config &domain, const std::string &account, const std::string &adminIn, const std::string &adminPassword) { if (account.empty()) throw std::runtime_error(domain.domain + ": no computer account configured (kerberos_principal)"); Config c = domain; c.auth = Auth::Kerberos; c.krbRealm = domain.auth == Auth::Kerberos && !domain.krbRealm.empty() ? domain.krbRealm : upper(domain.domain); adminPrincipal(adminIn, c.krbPrincipal, c.krbRealm); c.bindPassword = adminPassword; c.keytab.clear(); if (c.kdcs.empty()) c.kdcs = c.servers; return leaveWith(c, account); } JoinResult joinDomain(const std::string &domainIn, const std::string &adminIn, const std::string &adminPassword, const std::string &secretDir, int timeoutSec) { std::string domain = lower(domainIn); while (!domain.empty() && domain.back() == '.') domain.pop_back(); if (domain.empty()) throw std::runtime_error("no domain"); std::string admin, realm = upper(domain); adminPrincipal(adminIn, admin, realm); Config c; c.domain = domain; Loading