Commit 3fb18f51 authored by jan.koester's avatar jan.koester
Browse files

test

parent 81be1cad
Loading
Loading
Loading
Loading
+68 −3
Changes for src/daemon.cpp: 68 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -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"
@@ -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;
@@ -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();
@@ -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;
+3 −1
Changes for src/directory.cpp: 3 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -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;
}
+2 −2
Changes for src/directory.h: 2 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -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 */
+21 −0
Changes for src/module.cpp: 21 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -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>
@@ -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
@@ -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;
}
+4 −0
Changes for src/protocol.cpp: 4 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -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) {
@@ -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