Commit ccf0eae9 authored by jan.koester's avatar jan.koester
Browse files

test

parent 8baef6ab
Loading
Loading
Loading
Loading
+13 −4
Changes for src/acceptor.cpp: 13 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -73,14 +73,22 @@ Accepted Acceptor::accept(const Bytes &token) {
    try {
        /* SPNEGO (NegTokenInit) around a Kerberos token, or the Kerberos token itself */
        bool spnego = false;
        Bytes mechToken = token;
        Bytes mechToken = token, offered;  /* offered: the client's first mechanism, which the token is for */
        ber::Element outer = ber::Reader(token).next(0x60);
        Bytes oid = leadingOid(outer.content);
        if (oid == SpnegoOid) {
            spnego = true;
            ber::Element init = ber::Reader(outer.content.substr(oid.size())).next(0xa0);
            auto f = der::fields(ber::Reader(init.content).next(ber::Sequence).content);
            if (!f.count(2)) refuse("SPNEGO without a mechanism token (no Kerberos ticket: the client tried NTLM?)");
            if (f.count(0)) {
                ber::Reader mechs(ber::Reader(f[0]).next(ber::Sequence).content);
                if (!mechs.atEnd()) {
                    ber::Element first = mechs.next(0x06);
                    offered = ber::tlv(0x06, first.content);
                }
            }
            if (!f.count(2) || (offered != KrbOid && offered != MsKrbOid))
                refuse("SPNEGO without a Kerberos token first (the client tries NTLM? -- by address instead of name, or not a domain member)");
            mechToken = der::octets(f[2]);
            outer = ber::Reader(mechToken).next(0x60);
            oid = leadingOid(outer.content);
@@ -158,8 +166,9 @@ Accepted Acceptor::accept(const Bytes &token) {
                                                                            krb5::encrypt(sessionKey, UsageApRepEncPart, encPart)))));
        Bytes gss = ber::tlv(0x60, KrbOid + Bytes("\x02\x00", 2) + apRep);
        if (spnego) {
            /* NegTokenResp: accept-completed, the client's Kerberos mechanism, our token */
            a.response = ber::tlv(0xa1, ber::sequence(der::ctx(0, ber::enumerated(0)) + der::ctx(1, oid) +
            /* NegTokenResp: accept-completed, the mechanism the client put first
             * (Windows: Microsoft's OID, with a token of the standard one), our token */
            a.response = ber::tlv(0xa1, ber::sequence(der::ctx(0, ber::enumerated(0)) + der::ctx(1, offered) +
                                                      der::ctx(2, ber::octets(gss))));
        } else {
            a.response = gss;
+1 −0
Changes for src/directory.cpp: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -510,6 +510,7 @@ 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) */
    }
    return End;
}
+8 −5
Changes for src/home.cpp: 8 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -40,8 +40,11 @@ namespace adconnect {

namespace {

bool chownTo(int dirfd, const char *name, const uid_r_t &uid, const gid_r_t &gid) {
    return fchownat_r(dirfd, name, &uid, &gid, AT_SYMLINK_NOFOLLOW) == 0;
/* the *_r call; on a kernel without it, the classic one for classic ids */
bool chownTo(int dirfd, const char *name, const uid_r_t &uid, const gid_r_t &gid, int flags = AT_SYMLINK_NOFOLLOW) {
    if (fchownat_r(dirfd, name, &uid, &gid, flags) == 0) return true;
    if (errno != ENOSYS || !uid_r_is_legacy(&uid) || !uid_r_is_legacy(&gid)) return false;
    return fchownat(dirfd, name, (uid_t)uid.legacy, (gid_t)gid.legacy, flags) == 0;
}

/* src (a directory) into dst (a new, empty one), owned by uid/gid */
@@ -92,8 +95,8 @@ bool copyTree(int src, int dst, const uid_r_t &uid, const gid_r_t &gid) {

bool ensureHome(const std::string &dir, const std::string &uidText, const std::string &gidText, std::string &error,
                const std::string &skel) {
    if (dir.size() < 2 || dir[0] != '/' || dir.find("/../") != std::string::npos || dir.find("/./") != std::string::npos ||
        dir.compare(dir.size() - 3 < dir.size() ? dir.size() - 3 : 0, 3, "/..") == 0) {
    bool dotdot = dir.size() >= 3 && dir.compare(dir.size() - 3, 3, "/..") == 0;
    if (dir.size() < 2 || dir[0] != '/' || dir.find("/../") != std::string::npos || dir.find("/./") != std::string::npos || dotdot) {
        error = "not a home directory: " + dir;
        return false;
    }
@@ -129,7 +132,7 @@ bool ensureHome(const std::string &dir, const std::string &uidText, const std::s
        ok = copyTree(s, home, uid, gid);
        close(s);
    }
    ok = fchownat_r(home, "", &uid, &gid, AT_EMPTY_PATH) == 0 && ok;
    ok = chownTo(home, "", uid, gid, AT_EMPTY_PATH) && ok;
    close(home);
    if (!ok) error = dir + " was only partly set up";
    return ok;
+36 −3
Changes for test/test.cpp: 36 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "../src/acceptor.h"
#include "../src/directory.h"
#include "../src/discover.h"
#include "../src/home.h"
#include "../src/join.h"
#include "../src/gssapi.h"
#include "../src/krb5.h"
@@ -806,7 +807,8 @@ void testAcceptor() {
        CHECK(a.sessionKey.size() == 32 && a.sessionKey != sessionKey.value, "acceptor: the session key is the authenticator's subkey");
        /* NegTokenResp: accept-completed, the client's mechanism (MS OID first), the AP-REP */
        auto f = der::fields(ber::Reader(ber::Reader(a.response).next(0xa1).content).next(ber::Sequence).content);
        CHECK(ber::toInt(ber::Reader(f[0]).next(ber::Enumerated)) == 0 && f[1] == msKrb, "acceptor: NegTokenResp accept-completed, MS OID");
        CHECK(ber::toInt(ber::Reader(f[0]).next(ber::Enumerated)) == 0 && f[1] == msKrb,
              "acceptor: NegTokenResp accept-completed, the client's first mechanism (MS OID, token of the standard one)");
        client.processReply(der::octets(f[2]));
        CHECK(client.established(), "acceptor: our initiator takes the AP-REP (mutual authentication)");
    } catch (const std::exception &e) {
@@ -829,14 +831,44 @@ void testAcceptor() {
    expired.ticket = ticketFor(time(nullptr) - 3600, serviceKey);
    gss::Context x(expired);
    CHECK(throws([&] { acc.accept(spnego(x.initialToken())); }), "acceptor: an expired ticket refused");
    std::string ntlmOnly = ber::tlv(0x60, spnegoOid + ber::tlv(0xa0, ber::sequence(der::ctx(0, ber::sequence(krb)))));
    CHECK(throws([&] { acc.accept(ntlmOnly); }), "acceptor: SPNEGO without a Kerberos token refused");
    std::string noToken = ber::tlv(0x60, spnegoOid + ber::tlv(0xa0, ber::sequence(der::ctx(0, ber::sequence(krb)))));
    CHECK(throws([&] { acc.accept(noToken); }), "acceptor: SPNEGO without a Kerberos token refused");
    const std::string ntlmOid("\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a", 12);
    gss::Context n(cred);
    std::string ntlmFirst = ber::tlv(0x60, spnegoOid + ber::tlv(0xa0, ber::sequence(
        der::ctx(0, ber::sequence(ntlmOid + krb)) + der::ctx(2, ber::octets(n.initialToken())))));
    CHECK(throws([&] { acc.accept(ntlmFirst); }), "acceptor: NTLM first (its token isn't Kerberos) refused");

    std::string b;
    CHECK(base64Encode("hello!?") == "aGVsbG8hPw==" && base64Decode("aGVsbG8hPw==", b) && b == "hello!?" &&
          !base64Decode("a$==", b), "base64");
}

void testHome(const std::string &dir) {
    std::string skel = dir + "/skel", home = dir + "/home/example.com/jan", err;
    mkdir(skel.c_str(), 0755);
    mkdir((skel + "/.config").c_str(), 0755);
    std::ofstream(skel + "/.profile") << "export X=1\n";
    std::ofstream(skel + "/.config/app.conf") << "a=b\n";
    symlink(".profile", (skel + "/.bashrc").c_str());
    std::string me = std::to_string(getuid()), myGroup = std::to_string(getgid());
    CHECK(ensureHome(home, me, myGroup, err, skel), "home: made (" + err + ")");
    struct stat st;
    CHECK(stat(home.c_str(), &st) == 0 && S_ISDIR(st.st_mode) && (st.st_mode & 0777) == 0700 && st.st_uid == getuid(),
          "home: 0700, the user's");
    CHECK(stat((dir + "/home/example.com").c_str(), &st) == 0 && (st.st_mode & 0777) == 0755, "home: /home/<domain> made, 0755");
    std::ifstream in(home + "/.config/app.conf");
    std::string line;
    std::getline(in, line);
    char target[64] = "";
    ssize_t n = readlink((home + "/.bashrc").c_str(), target, sizeof target - 1);
    CHECK(line == "a=b" && n > 0 && std::string(target, (size_t)n) == ".profile", "home: /etc/skel copied, symlinks as links");
    std::ofstream(home + "/mine") << "keep\n";
    CHECK(ensureHome(home, me, myGroup, err, skel) && stat((home + "/mine").c_str(), &st) == 0, "home: there already, left alone");
    CHECK(!ensureHome("/home/../etc", me, myGroup, err, skel) && !ensureHome("relative", me, myGroup, err, skel),
          "home: no .. and only absolute paths");
}

time_t fakeNow = 1000;

void testCache(const std::string &dir) {
@@ -1081,6 +1113,7 @@ int main() {
    testConfig(dir);
    testCache(dir);
    testAcceptor();
    testHome(dir);
    testDiscover();
    testJoin(dir);
    testKrb5Crypto();