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

test

parent 9d99d5bb
Loading
Loading
Loading
Loading
+216 −9
Changes for src/smb.cpp: 216 added lines, 9 removed lines.
Original line number Diff line number Diff line
@@ -84,6 +84,8 @@ enum Event {
    LoginResponseExt = 17,
    NtlmAuthenRequest = 18,
    NtlmAuthenResponse = 19,
    SidMapRequest = 20,
    SidMapResponse = 21,
};

constexpr size_t AccountSize = 48, HashSize = 18, ShareSize = 64;
@@ -226,6 +228,31 @@ struct NtlmAuthenResponseMsg {
    uint32_t reserved[16];
};

// ksmbd_sid_map_request/response: a user's or group's id <-> its Windows
// SID (objectSid, from adconnectd), for the owners and ACL entries of
// security descriptors -- any account, logged in or not. Asked only with
// GlobalFlagSidMap in the startup request.
constexpr uint32_t GlobalFlagSidMap = 1u << 5;
constexpr uint32_t SidMapToSid = 0x1;  // id -> SID, else SID -> id
constexpr uint32_t SidMapGroup = 0x2;  // the id is a gid
enum SidMapStatus : uint32_t { SidMapOk = 0, SidMapNone = 1, SidMapUnavailable = 2 };
constexpr size_t SidSize = 68;  // binary SID: 8 bytes + 15 sub-authorities
struct SidMapRequestMsg {
    uint32_t handle;
    uint32_t flags;
    IdR id;                       // SidMapToSid
    unsigned char sid[SidSize];   // else
    uint32_t reserved[16];
};
struct SidMapResponseMsg {
    uint32_t handle;
    uint32_t status;              // SidMapStatus
    uint32_t flags;               // SidMapGroup: id is a gid
    IdR id;
    unsigned char sid[SidSize];
    uint32_t reserved[16];
};

// the kernel accepts a response only at exactly its struct's size
static_assert(sizeof(StartupRequestMsg) == 744, "ksmbd_startup_request");
static_assert(sizeof(LoginRequestMsg) == 116, "ksmbd_login_request");
@@ -239,6 +266,8 @@ static_assert(sizeof(RpcCommandMsg) == 12, "ksmbd_rpc_command");
static_assert(sizeof(SpnegoAuthenResponseMsg) == 168, "ksmbd_spnego_authen_response");
static_assert(sizeof(NtlmAuthenRequestMsg) == 88, "ksmbd_ntlm_authen_request");
static_assert(sizeof(NtlmAuthenResponseMsg) == 88, "ksmbd_ntlm_authen_response");
static_assert(sizeof(SidMapRequestMsg) == 160, "ksmbd_sid_map_request");
static_assert(sizeof(SidMapResponseMsg) == 164, "ksmbd_sid_map_response");

enum : uint16_t {
    UserOk = 1 << 0,
@@ -491,17 +520,22 @@ std::vector<gid_t> groupsOf(const char *name, gid_t gid) {
#endif

#ifdef SHAREDD_UID_R
// systempp's getgrouplist() for _UID_R_SOURCE programs: the groups of
// group.yaml and of every <userdb.h> source -- adconnect's GROUPS, so
// nested AD groups too, and without enumerating the domain
extern "C" int __uid_r_getgrouplist(const char *user, gid_r_t group, gid_r_t *groups, int *ngroups);

// all groups of an account (primary first), UUID and GUID ones too
std::vector<gid_r_t> groupsOfR(const Account &a) {
    std::vector<gid_r_t> groups{a.rgid};
    setgrent_id_r();
    while (struct group_id_r *gr = getgrent_id_r()) {
        bool member = false, have = false;
        for (char **m = gr->gr_mem; m && *m; ++m) member = member || a.name == *m;
        for (auto &g : groups) have = have || uid_r_eq(&g, &gr->gr_gid);
        if (member && !have) groups.push_back(gr->gr_gid);
    }
    endgrent_id_r();
    std::vector<gid_r_t> groups(64);
    int n = (int)groups.size();
    int rc = __uid_r_getgrouplist(a.name.c_str(), a.rgid, groups.data(), &n);
    if (rc < 0 && n > (int)groups.size()) {
        groups.resize((size_t)n);
        rc = __uid_r_getgrouplist(a.name.c_str(), a.rgid, groups.data(), &n);
    }
    groups.resize(rc > 0 ? (size_t)rc : 0);
    if (groups.empty()) groups.push_back(a.rgid);
    return groups;
}

@@ -709,6 +743,7 @@ bool SmbServer::start(std::string &err) {
    req.tcp_port = 445;
    req.ipc_timeout = 0;     // no heartbeat watchdog
    req.file_max = 10000;    // 0 would allow no open files at all
    req.flags |= GlobalFlagSidMap;  // we answer SID mapping requests (sidMap())
    subAuth(req.sub_auth);

    // a reset of a previous instance may still be running: retry a little
@@ -779,6 +814,7 @@ void SmbServer::handle(int cmd, const unsigned char *payload, size_t len) {
        case RpcRequest: rpc(payload, len); break;
        case SpnegoAuthenRequest: spnego(payload, len); break;
        case NtlmAuthenRequest: ntlmAuthen(payload, len); break;
        case SidMapRequest: sidMap(payload, len); break;
        default: break;  // heartbeat, tree disconnect, logout: nothing to answer
    }
}
@@ -1160,6 +1196,93 @@ bool ntlmViaAdconnect(const std::string &domain, const std::string &account, con
    return true;
}

// one request to adconnectd, its whole answer; false if it couldn't be
// asked (not running, no answer within timeout seconds)
bool adconnectQuery(const std::string &line, std::string &reply, int timeout) {
    int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
    if (fd < 0) return false;
    sockaddr_un a{};
    a.sun_family = AF_UNIX;
    snprintf(a.sun_path, sizeof a.sun_path, "%s", SHAREDD_ADCONNECT_SOCKET);
    struct timeval tv = {timeout, 0};
    setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
    setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
    if (connect(fd, (sockaddr *)&a, sizeof a) != 0) {
        close(fd);
        return false;
    }
    std::string req = line + "\n";
    bool ok = ::send(fd, req.data(), req.size(), MSG_NOSIGNAL) == (ssize_t)req.size();
    reply.clear();
    char buf[4096];
    ssize_t n = 0;
    while (ok && (n = recv(fd, buf, sizeof buf, 0)) > 0) reply.append(buf, (size_t)n);
    close(fd);
    return ok && n == 0;
}

// the tab-separated fields of the first record of an adconnectd answer
std::vector<std::string> firstRecord(const std::string &reply) {
    std::vector<std::string> f;
    std::string line = reply.substr(0, reply.find('\n'));
    size_t p = 0;
    for (size_t q; (q = line.find('\t', p)) != std::string::npos; p = q + 1) f.push_back(line.substr(p, q - p));
    f.push_back(line.substr(p));
    return f;
}

// a binary SID (revision 1, at most 15 sub-authorities) as S-1-...; "" if malformed
std::string sidText(const unsigned char *b, size_t size) {
    if (size < 8 || b[0] != 1 || b[1] > 15 || size < 8 + 4 * (size_t)b[1]) return std::string();
    unsigned long long auth = 0;
    for (int i = 2; i < 8; i++) auth = auth << 8 | b[i];
    std::string s = "S-1-" + std::to_string(auth);
    for (int i = 0; i < b[1]; i++) {
        const unsigned char *p = b + 8 + 4 * i;
        s += "-" + std::to_string((uint32_t)p[0] | (uint32_t)p[1] << 8 | (uint32_t)p[2] << 16 | (uint32_t)p[3] << 24);
    }
    return s;
}

// S-1-... into out[SidSize]; false if malformed
bool sidBinary(const std::string &t, unsigned char out[SidSize]) {
    if (t.compare(0, 4, "S-1-") != 0) return false;
    std::vector<unsigned long long> parts;
    for (size_t p = 4;;) {
        size_t q = t.find('-', p);
        std::string part = t.substr(p, q == std::string::npos ? std::string::npos : q - p);
        if (part.empty() || part.size() > 15 || part.find_first_not_of("0123456789") != std::string::npos) return false;
        parts.push_back(strtoull(part.c_str(), nullptr, 10));
        if (q == std::string::npos) break;
        p = q + 1;
    }
    if (parts.size() < 2 || parts.size() > 16 || parts[0] >= 1ULL << 48) return false;
    memset(out, 0, SidSize);
    out[0] = 1;
    out[1] = (unsigned char)(parts.size() - 1);
    for (int i = 0; i < 6; i++) out[2 + i] = (unsigned char)(parts[0] >> (8 * (5 - i)));
    for (size_t i = 1; i < parts.size(); i++) {
        if (parts[i] > 0xffffffffULL) return false;
        for (int k = 0; k < 4; k++) out[8 + 4 * (i - 1) + k] = (unsigned char)(parts[i] >> (8 * k));
    }
    return true;
}

// whether acct, found by name, is the account adconnectd has under that
// name (the same uid) -- and not a local one of the same name
bool sameAsDirectory(const std::string &name, const Account &acct) {
    std::string reply;
    if (!adconnectQuery("PWNAM " + name, reply, 10)) return false;
    std::vector<std::string> f = firstRecord(reply);
    if (f.size() != 7 || f[0] != "U") return false;
#ifdef SHAREDD_UID_R
    char buf[UID_R_STR_LEN];
    return f[2] == uid_r_to_str(&acct.ruid, buf);
#else
    return f[2] == std::to_string(acct.uid);
#endif
}

} // namespace

// Kerberos logins (domain members asking for cifs/<this system>): the
@@ -1184,6 +1307,12 @@ void SmbServer::spnego(const unsigned char *payload, size_t len) {
        error = "no such user " + k.name;
        ok = false;
    }
    // the ticket is for the domain's account: it must not land on a local
    // account that merely has the same name (user.yaml comes first)
    if (ok && !sameAsDirectory(k.name, acct)) {
        error = k.name + " is a domain user, but the name is a local account here";
        ok = false;
    }
    if (ok && ((!acct.idR && acct.uid == 0) || locked(acct.name.c_str()))) {
        error = acct.name + (locked(acct.name.c_str()) ? ": account locked" : ": root may not log in over SMB");
        ok = false;
@@ -1259,4 +1388,82 @@ void SmbServer::ntlmAuthen(const unsigned char *payload, size_t len) {
    send(NtlmAuthenResponse, &resp, sizeof(resp), false);
}

// ksmbd asks for the SID of an id (a file's owner or group, an ACL entry)
// or the id of a SID (an owner or ACE a client sets). adconnectd knows the
// domain accounts' objectSids (SIDOF / IDOF); for anything else ksmbd keeps
// its own S-1-22 SIDs. SidMapNone: no such account or no objectSid (the
// kernel may cache that), SidMapUnavailable: the directory couldn't be
// asked (it mustn't). adconnectd's own cache and offline cache apply.
void SmbServer::sidMap(const unsigned char *payload, size_t len) {
    if (len < sizeof(SidMapRequestMsg)) return;
    SidMapRequestMsg req;
    memcpy(&req, payload, sizeof(req));
    SidMapResponseMsg resp;
    memset(&resp, 0, sizeof(resp));
    resp.handle = req.handle;
    resp.status = SidMapNone;
    resp.flags = req.flags & SidMapGroup;

    std::string line;
    if (req.flags & SidMapToSid) {
#ifdef SHAREDD_UID_R
        uid_r_t id;
        memset(&id, 0, sizeof(id));
        id.type = req.id.type;
        memcpy(&id.legacy, req.id.id, id.type == UID_R_LEGACY ? sizeof(id.legacy) : sizeof(req.id.id));
        char buf[UID_R_STR_LEN];
        std::string text = uid_r_to_str(&id, buf);
#else
        uint32_t legacy;
        memcpy(&legacy, req.id.id, sizeof(legacy));
        std::string text = req.id.type == 1 ? std::to_string(legacy) : std::string();
#endif
        if (!text.empty()) line = std::string("SIDOF ") + (req.flags & SidMapGroup ? "g " : "u ") + text;
        resp.id = req.id;
    } else {
        std::string text = sidText(req.sid, sizeof(req.sid));
        if (!text.empty()) line = "IDOF " + text;
        memcpy(resp.sid, req.sid, sizeof(resp.sid));
    }

    std::string reply;
    if (line.empty() || !adconnectPresent()) {
        // nothing to ask: no such mapping
    } else if (!adconnectQuery(line, reply, 2)) {
        resp.status = SidMapUnavailable;  // the kernel waits about as long
    } else if (reply == "END\n") {
        // no account, or one without an objectSid
    } else if (reply.compare(0, 4, "ERR ") == 0) {
        resp.status = SidMapUnavailable;
        std::cerr << "sharedd: SMB: " << line << ": " << reply.substr(4, reply.find('\n') - 4) << std::endl;
    } else {
        std::vector<std::string> f = firstRecord(reply);
        bool group = f.size() == 4 && f[1] == "g";
        bool good = f.size() == 4 && f[0] == "S" && (group || f[1] == "u") &&
                    reply.size() >= 4 && reply.compare(reply.size() - 4, 4, "END\n") == 0;
        if (good && (req.flags & SidMapToSid)) {
            good = sidBinary(f[3], resp.sid);
        } else if (good) {
#ifdef SHAREDD_UID_R
            uid_r_t id;
            good = uid_r_from_str(f[2].c_str(), &id) == 0 && (uid_r_is_legacy(&id) || kernelHasUidR());
            if (good) resp.id = toIdR(id);
#else
            char *end;
            unsigned long v = strtoul(f[2].c_str(), &end, 10);
            good = !f[2].empty() && !*end && v <= 0xfffffffeUL;
            if (good) {
                resp.id.type = 1;
                uint32_t legacy = (uint32_t)v;
                memcpy(resp.id.id, &legacy, sizeof(legacy));
            }
#endif
            resp.flags = group ? SidMapGroup : 0;
        }
        if (good) resp.status = SidMapOk;
        else std::cerr << "sharedd: SMB: " << line << ": bad answer from adconnectd" << std::endl;
    }
    send(SidMapResponse, &resp, sizeof(resp), false);
}

} // namespace sharedd
+1 −0
Changes for src/smb.h: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -84,6 +84,7 @@ private:
    void handle(int cmd, const unsigned char *payload, size_t len);
    void login(const unsigned char *payload, size_t len);
    void loginExt(const unsigned char *payload, size_t len);
    void sidMap(const unsigned char *payload, size_t len);
    void shareConfig(const unsigned char *payload, size_t len);
    void treeConnect(const unsigned char *payload, size_t len);
    void rpc(const unsigned char *payload, size_t len);