Loading src/daemon.cpp +68 −3 Changes for src/daemon.cpp: 68 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -62,6 +62,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "config.h" #include "acceptor.h" #include "directory.h" #include "gssapi.h" #include "home.h" #include "join.h" #include "netlogon.h" Loading Loading @@ -221,6 +222,67 @@ bool ask(Domain &d, Cmd cmd, const std::string &arg, const std::string &line, st return false; } /* AUTH: a domain user's password, for su, login and sshd (adconnect.so's * authenticate). A TGT with the password alone would also come from a KDC * someone faked, so the ticket it gets for our own computer account must * pass our acceptor (our account's key) too. The arg is base64 of * "user NUL password"; the password is never logged. */ std::string acceptAuth(Server &s, const std::string &arg) { std::string blob; if (!base64Decode(arg, blob)) return formatError("bad AUTH request"); size_t nul = blob.find('\0'); if (nul == std::string::npos || nul == 0) return formatError("bad AUTH request"); std::string user = blob.substr(0, nul), password = blob.substr(nul + 1); if (!clean(user)) return formatError("bad AUTH request"); Domain *d = nullptr; for (auto &dp : s.domains) if (dp->dir.owns(user)) { d = dp.get(); break; } if (!d) return End; /* no user of our domains */ if (d->cfg.auth != Auth::Kerberos || d->cfg.krbPrincipal.empty()) return formatError(d->cfg.domain + " is not joined"); /* only users the directory hands out (the user filter) */ std::string records, error; if (!ask(*d, Cmd::PwNam, user, "AUTH " + user, records, error)) return formatError(error); Response r; if (!parseResponse(records + End, r) || r.users.empty()) return End; std::string account = d->dir.account(user); try { krb5::Client c(d->cfg.kdcs, d->cfg.timeout * 1000); c.setPassword(krb5::Principal{ krb5::NtPrincipal, { account }, d->cfg.krbRealm }, password); krb5::Credentials cred = c.serviceTicket(krb5::Principal{ krb5::NtPrincipal, { d->cfg.krbPrincipal }, d->cfg.krbRealm }); gss::Context ctx(cred); Accepted a = s.acceptor->accept(ctx.initialToken()); auto same = [](const std::string &x, const std::string &y) { return x.size() == y.size() && std::equal(x.begin(), x.end(), y.begin(), [](char p, char q) { return tolower((unsigned char)p) == tolower((unsigned char)q); }); }; if (a.client.names.size() != 1 || !same(a.client.names[0], account) || !same(a.client.realm, d->cfg.krbRealm)) return "A\tdenied\tthe ticket is for " + a.client.str() + "\n" + End; return std::string("A\tok\n") + End; } catch (const krb5::Error &e) { if (e.code() == -1) { std::cerr << "adconnectd: AUTH " << user << ": " << e.what() << std::endl; return formatError(e.what()); } std::string why = e.what(); for (char &ch : why) if ((unsigned char)ch < 0x20 || ch == 0x7f) ch = ' '; std::cerr << "adconnectd: AUTH " << user << ": denied: " << why << std::endl; return "A\tdenied\t" + why + "\n" + End; } catch (const std::exception &e) { std::cerr << "adconnectd: AUTH " << user << ": " << e.what() << std::endl; return formatError(e.what()); } } /* the whole answer from the domains, uncached; false and *error if a domain it needed failed */ bool resolve(Server &s, Cmd cmd, const std::string &arg, const std::string &line, std::string &out, std::string &error) { std::string records; Loading Loading @@ -266,6 +328,9 @@ bool resolve(Server &s, Cmd cmd, const std::string &arg, const std::string &line case Cmd::Ntlm: out = acceptNtlm(s, arg); return true; case Cmd::Auth: out = acceptAuth(s, arg); return true; case Cmd::PwEnt: case Cmd::GrEnt: out.clear(); Loading @@ -284,9 +349,9 @@ std::string answer(Server &s, const std::string &line, uid_t peer) { std::string arg; if (!parseRequest(line, cmd, arg)) return formatError("bad request"); std::string out, error; if (cmd == Cmd::Krb5 || cmd == Cmd::Ntlm) { /* a session key for whoever brings a ticket or NTLM response: root * only (SO_PEERCRED), never cached */ if (cmd == Cmd::Krb5 || cmd == Cmd::Ntlm || cmd == Cmd::Auth) { /* a session key for whoever brings a ticket or NTLM response, a * password check: root only (SO_PEERCRED), never cached */ if (peer != 0) return formatError("permission denied"); resolve(s, cmd, arg, line, out, error); return out; Loading src/directory.cpp +3 −1 Changes for src/directory.cpp: 3 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -515,7 +515,9 @@ std::string Directory::handle(Cmd cmd, const std::string &arg) { case Cmd::GrGid: return groupById(arg); case Cmd::GrEnt: return allGroups(); case Cmd::Groups: return groupsOf(arg); case Cmd::Krb5: break; /* the daemon's (acceptor.h) */ case Cmd::Krb5: case Cmd::Ntlm: case Cmd::Auth: break; /* the daemon's (acceptor.h, netlogon.h) */ } return End; } Loading src/directory.h +2 −2 Changes for src/directory.h: 2 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -118,14 +118,14 @@ public: void check() { domainSid(); } /* a user or group name this domain answers for (qualified or not) */ bool owns(const std::string &name) const { return validName(name); } /* the sAMAccountName part of one of our names */ std::string account(const std::string &name) const; private: std::vector<ldap::Entry> find(const std::string &filter, const std::vector<std::string> &attrs); std::string userFilter(const std::string &extra) const; std::string groupFilter(const std::string &extra) const; bool validName(const std::string &name) const; /* the sAMAccountName part of one of our names */ std::string account(const std::string &name) const; std::string name(const ldap::Entry &e) const; const std::string &domainSid(); /* the id of a user (uid) or group (gid), "" if it has none */ Loading src/module.cpp +21 −0 Changes for src/module.cpp: 21 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * socket: /run/adconnect.sock # default * timeout: 30 # seconds, default 30 */ #include <algorithm> #include <cerrno> #include <cstdint> #include <cstdlib> Loading Loading @@ -274,6 +275,25 @@ int getgroups_(void *ctx, const char *user, gid_r_t *list, size_t *n) { } } /* su, login, sshd: adconnectd asks the user's KDC (root only) */ int authenticate_(void *ctx, const char *user, const char *password) { try { if (!clean(user)) return ENOENT; std::string blob = std::string(user) + '\0' + password; std::string req = request(Cmd::Auth, base64Encode(blob)); std::fill(blob.begin(), blob.end(), '\0'); Response r; int e = query((Ctx *)ctx, req, r); std::fill(req.begin(), req.end(), '\0'); if (e) return e; if (r.auth == "ok") return 0; if (r.auth == "denied") return EACCES; return ENOENT; } catch (...) { return EIO; } } void fini(void *ctx) { delete (Ctx *)ctx; } } // namespace Loading Loading @@ -308,5 +328,6 @@ extern "C" int systempp_userdb_init(const struct userdb_option *opts, size_t nop mod->grent_next = grent_next; mod->grent_close = ent_close; mod->getgroups = getgroups_; mod->authenticate = authenticate_; return 0; } src/protocol.cpp +4 −0 Changes for src/protocol.cpp: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ const Name Names[] = { { Cmd::PwNam, "PWNAM", true }, { Cmd::PwUid, "PWUID", true }, { Cmd::PwEnt, "PWENT", false }, { Cmd::GrNam, "GRNAM", true }, { Cmd::GrGid, "GRGID", true }, { Cmd::GrEnt, "GRENT", false }, { Cmd::Groups, "GROUPS", true }, { Cmd::Krb5, "KRB5", true }, { Cmd::Ntlm, "NTLM", true }, { Cmd::Auth, "AUTH", true }, }; bool printable(const std::string &s) { Loading Loading @@ -179,6 +180,9 @@ bool parseResponse(const std::string &text, Response &r, std::string *error) { r.groups.push_back(std::move(g)); } else if (f[0] == "I" && f.size() == 2) { r.gids.push_back(f[1]); } else if (f[0] == "A" && (f.size() == 2 || f.size() == 3) && (f[1] == "ok" || f[1] == "denied")) { r.auth = f[1]; if (f.size() == 3) r.authWhy = f[2]; } else { break; } Loading Loading
src/daemon.cpp +68 −3 Changes for src/daemon.cpp: 68 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -62,6 +62,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "config.h" #include "acceptor.h" #include "directory.h" #include "gssapi.h" #include "home.h" #include "join.h" #include "netlogon.h" Loading Loading @@ -221,6 +222,67 @@ bool ask(Domain &d, Cmd cmd, const std::string &arg, const std::string &line, st return false; } /* AUTH: a domain user's password, for su, login and sshd (adconnect.so's * authenticate). A TGT with the password alone would also come from a KDC * someone faked, so the ticket it gets for our own computer account must * pass our acceptor (our account's key) too. The arg is base64 of * "user NUL password"; the password is never logged. */ std::string acceptAuth(Server &s, const std::string &arg) { std::string blob; if (!base64Decode(arg, blob)) return formatError("bad AUTH request"); size_t nul = blob.find('\0'); if (nul == std::string::npos || nul == 0) return formatError("bad AUTH request"); std::string user = blob.substr(0, nul), password = blob.substr(nul + 1); if (!clean(user)) return formatError("bad AUTH request"); Domain *d = nullptr; for (auto &dp : s.domains) if (dp->dir.owns(user)) { d = dp.get(); break; } if (!d) return End; /* no user of our domains */ if (d->cfg.auth != Auth::Kerberos || d->cfg.krbPrincipal.empty()) return formatError(d->cfg.domain + " is not joined"); /* only users the directory hands out (the user filter) */ std::string records, error; if (!ask(*d, Cmd::PwNam, user, "AUTH " + user, records, error)) return formatError(error); Response r; if (!parseResponse(records + End, r) || r.users.empty()) return End; std::string account = d->dir.account(user); try { krb5::Client c(d->cfg.kdcs, d->cfg.timeout * 1000); c.setPassword(krb5::Principal{ krb5::NtPrincipal, { account }, d->cfg.krbRealm }, password); krb5::Credentials cred = c.serviceTicket(krb5::Principal{ krb5::NtPrincipal, { d->cfg.krbPrincipal }, d->cfg.krbRealm }); gss::Context ctx(cred); Accepted a = s.acceptor->accept(ctx.initialToken()); auto same = [](const std::string &x, const std::string &y) { return x.size() == y.size() && std::equal(x.begin(), x.end(), y.begin(), [](char p, char q) { return tolower((unsigned char)p) == tolower((unsigned char)q); }); }; if (a.client.names.size() != 1 || !same(a.client.names[0], account) || !same(a.client.realm, d->cfg.krbRealm)) return "A\tdenied\tthe ticket is for " + a.client.str() + "\n" + End; return std::string("A\tok\n") + End; } catch (const krb5::Error &e) { if (e.code() == -1) { std::cerr << "adconnectd: AUTH " << user << ": " << e.what() << std::endl; return formatError(e.what()); } std::string why = e.what(); for (char &ch : why) if ((unsigned char)ch < 0x20 || ch == 0x7f) ch = ' '; std::cerr << "adconnectd: AUTH " << user << ": denied: " << why << std::endl; return "A\tdenied\t" + why + "\n" + End; } catch (const std::exception &e) { std::cerr << "adconnectd: AUTH " << user << ": " << e.what() << std::endl; return formatError(e.what()); } } /* the whole answer from the domains, uncached; false and *error if a domain it needed failed */ bool resolve(Server &s, Cmd cmd, const std::string &arg, const std::string &line, std::string &out, std::string &error) { std::string records; Loading Loading @@ -266,6 +328,9 @@ bool resolve(Server &s, Cmd cmd, const std::string &arg, const std::string &line case Cmd::Ntlm: out = acceptNtlm(s, arg); return true; case Cmd::Auth: out = acceptAuth(s, arg); return true; case Cmd::PwEnt: case Cmd::GrEnt: out.clear(); Loading @@ -284,9 +349,9 @@ std::string answer(Server &s, const std::string &line, uid_t peer) { std::string arg; if (!parseRequest(line, cmd, arg)) return formatError("bad request"); std::string out, error; if (cmd == Cmd::Krb5 || cmd == Cmd::Ntlm) { /* a session key for whoever brings a ticket or NTLM response: root * only (SO_PEERCRED), never cached */ if (cmd == Cmd::Krb5 || cmd == Cmd::Ntlm || cmd == Cmd::Auth) { /* a session key for whoever brings a ticket or NTLM response, a * password check: root only (SO_PEERCRED), never cached */ if (peer != 0) return formatError("permission denied"); resolve(s, cmd, arg, line, out, error); return out; Loading
src/directory.cpp +3 −1 Changes for src/directory.cpp: 3 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -515,7 +515,9 @@ std::string Directory::handle(Cmd cmd, const std::string &arg) { case Cmd::GrGid: return groupById(arg); case Cmd::GrEnt: return allGroups(); case Cmd::Groups: return groupsOf(arg); case Cmd::Krb5: break; /* the daemon's (acceptor.h) */ case Cmd::Krb5: case Cmd::Ntlm: case Cmd::Auth: break; /* the daemon's (acceptor.h, netlogon.h) */ } return End; } Loading
src/directory.h +2 −2 Changes for src/directory.h: 2 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -118,14 +118,14 @@ public: void check() { domainSid(); } /* a user or group name this domain answers for (qualified or not) */ bool owns(const std::string &name) const { return validName(name); } /* the sAMAccountName part of one of our names */ std::string account(const std::string &name) const; private: std::vector<ldap::Entry> find(const std::string &filter, const std::vector<std::string> &attrs); std::string userFilter(const std::string &extra) const; std::string groupFilter(const std::string &extra) const; bool validName(const std::string &name) const; /* the sAMAccountName part of one of our names */ std::string account(const std::string &name) const; std::string name(const ldap::Entry &e) const; const std::string &domainSid(); /* the id of a user (uid) or group (gid), "" if it has none */ Loading
src/module.cpp +21 −0 Changes for src/module.cpp: 21 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * socket: /run/adconnect.sock # default * timeout: 30 # seconds, default 30 */ #include <algorithm> #include <cerrno> #include <cstdint> #include <cstdlib> Loading Loading @@ -274,6 +275,25 @@ int getgroups_(void *ctx, const char *user, gid_r_t *list, size_t *n) { } } /* su, login, sshd: adconnectd asks the user's KDC (root only) */ int authenticate_(void *ctx, const char *user, const char *password) { try { if (!clean(user)) return ENOENT; std::string blob = std::string(user) + '\0' + password; std::string req = request(Cmd::Auth, base64Encode(blob)); std::fill(blob.begin(), blob.end(), '\0'); Response r; int e = query((Ctx *)ctx, req, r); std::fill(req.begin(), req.end(), '\0'); if (e) return e; if (r.auth == "ok") return 0; if (r.auth == "denied") return EACCES; return ENOENT; } catch (...) { return EIO; } } void fini(void *ctx) { delete (Ctx *)ctx; } } // namespace Loading Loading @@ -308,5 +328,6 @@ extern "C" int systempp_userdb_init(const struct userdb_option *opts, size_t nop mod->grent_next = grent_next; mod->grent_close = ent_close; mod->getgroups = getgroups_; mod->authenticate = authenticate_; return 0; }
src/protocol.cpp +4 −0 Changes for src/protocol.cpp: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ const Name Names[] = { { Cmd::PwNam, "PWNAM", true }, { Cmd::PwUid, "PWUID", true }, { Cmd::PwEnt, "PWENT", false }, { Cmd::GrNam, "GRNAM", true }, { Cmd::GrGid, "GRGID", true }, { Cmd::GrEnt, "GRENT", false }, { Cmd::Groups, "GROUPS", true }, { Cmd::Krb5, "KRB5", true }, { Cmd::Ntlm, "NTLM", true }, { Cmd::Auth, "AUTH", true }, }; bool printable(const std::string &s) { Loading Loading @@ -179,6 +180,9 @@ bool parseResponse(const std::string &text, Response &r, std::string *error) { r.groups.push_back(std::move(g)); } else if (f[0] == "I" && f.size() == 2) { r.gids.push_back(f[1]); } else if (f[0] == "A" && (f.size() == 2 || f.size() == 3) && (f[1] == "ok" || f[1] == "denied")) { r.auth = f[1]; if (f.size() == 3) r.authWhy = f[2]; } else { break; } Loading