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

test

parent 5a0ea224
Loading
Loading
Loading
Loading
+383 −2
Changes for src/rpc.cpp: 383 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -29,6 +29,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include <algorithm>
#include <cstring>
#include <utility>

#include <grp.h>
#include <pwd.h>

#if __has_include(<sys/uid_r.h>)
#include <sys/uid_r.h>  // systempp: UUID and GUID users
#define SHAREDD_UID_R 1
#endif

#include "adquery.h"

namespace sharedd {

@@ -226,6 +237,246 @@ struct ShareEntry {
    std::string remark;
};


// ---- lsarpc: SIDs <-> names for Windows' security dialogs ----

enum : uint16_t { SidUser = 1, SidGroup = 2, SidDomain = 3, SidAlias = 4, SidWknGroup = 5, SidUnknown = 8 };
constexpr uint32_t StSomeNotMapped = 0x00000107, StNoneMapped = 0xC0000073;

using Sid = std::vector<uint8_t>;  // binary

Sid makeSid(uint64_t auth, const std::vector<uint32_t> &subs) {
    Sid s = {1, (uint8_t)subs.size()};
    for (int i = 5; i >= 0; --i) s.push_back((uint8_t)(auth >> (8 * i)));
    for (uint32_t v : subs)
        for (int i = 0; i < 4; ++i) s.push_back((uint8_t)(v >> (8 * i)));
    return s;
}
uint64_t sidAuth(const Sid &s) {
    uint64_t a = 0;
    for (int i = 2; i < 8; ++i) a = a << 8 | s[i];
    return a;
}
std::vector<uint32_t> sidSubs(const Sid &s) {
    std::vector<uint32_t> out;
    for (size_t off = 8; off + 4 <= s.size(); off += 4) out.push_back(le32(&s[off]));
    return out;
}
Sid sidDomain(const Sid &s) {
    std::vector<uint32_t> subs = sidSubs(s);
    if (!subs.empty()) subs.pop_back();
    return makeSid(sidAuth(s), subs);
}

struct Translation {
    uint16_t type = SidUnknown;
    std::string name;
    Sid sid;
    int dom = -1;
};

struct RefDomains {
    std::vector<std::pair<std::string, Sid>> list;
    int add(const std::string &name, const Sid &sid) {
        for (size_t i = 0; i < list.size(); ++i)
            if (list[i].second == sid) return (int)i;
        list.push_back({name, sid});
        return (int)list.size() - 1;
    }
};

struct WellKnown {
    uint64_t auth;
    std::vector<uint32_t> subs;
    const char *domain, *name;
    uint16_t type;
};
const std::vector<WellKnown> &wellKnown() {
    static const std::vector<WellKnown> w = {
        {1, {0}, "", "Everyone", SidWknGroup},
        {3, {0}, "", "CREATOR OWNER", SidWknGroup},
        {3, {1}, "", "CREATOR GROUP", SidWknGroup},
        {5, {2}, "NT AUTHORITY", "NETWORK", SidWknGroup},
        {5, {7}, "NT AUTHORITY", "ANONYMOUS LOGON", SidWknGroup},
        {5, {11}, "NT AUTHORITY", "Authenticated Users", SidWknGroup},
        {5, {18}, "NT AUTHORITY", "SYSTEM", SidWknGroup},
        {5, {32, 544}, "BUILTIN", "Administrators", SidAlias},
        {5, {32, 545}, "BUILTIN", "Users", SidAlias},
        {5, {32, 546}, "BUILTIN", "Guests", SidAlias},
    };
    return w;
}

// FenrirOS' own SIDs (ksmbd): S-1-22-1-<uid>, S-1-22-2-<gid>, and
// S-1-22-<kind>-w0-w1-w2-w3 for UUID (3 user, 4 group) and GUID (5, 6) ids
constexpr const char *UnixUser = "Unix User", *UnixGroup = "Unix Group";

#ifdef SHAREDD_UID_R
std::string idName(const uid_r_t &id, bool group) {
    if (group) {
        struct group_id_r *gr = getgrgid_id_r(&id);
        return gr && gr->gr_name ? gr->gr_name : std::string();
    }
    struct passwd_id_r *pw = getpwuid_id_r(&id);
    return pw && pw->pw_name ? pw->pw_name : std::string();
}

// a domain account's objectSid (adconnect SIDOF), empty if it has none
Sid objectSid(const uid_r_t &id, bool group) {
    char buf[UID_R_STR_LEN];
    std::string reply;
    if (!adconnectQuery(std::string("SIDOF ") + (group ? "g " : "u ") + uid_r_to_str(&id, buf), reply, 2)) return {};
    std::vector<std::string> f = firstRecord(reply);
    unsigned char bin[SidBinSize];
    if (f.size() != 4 || f[0] != "S" || !sidBinary(f[3], bin)) return {};
    return Sid(bin, bin + 8 + 4 * bin[1]);
}

// the SID ksmbd shows for an id without an objectSid
Sid nativeSid(const uid_r_t &id, bool group) {
    if (id.type == UID_R_LEGACY) return makeSid(22, {group ? 2u : 1u, id.legacy});
    std::vector<uint32_t> subs = {(id.type == UID_R_UUID ? 3u : 5u) + (group ? 1u : 0u)};
    for (int i = 0; i < 4; ++i)
        subs.push_back((uint32_t)id.uuid[4 * i] << 24 | (uint32_t)id.uuid[4 * i + 1] << 16 |
                       (uint32_t)id.uuid[4 * i + 2] << 8 | id.uuid[4 * i + 3]);
    return makeSid(22, subs);
}
#endif

Translation translateSid(const Sid &sid, const std::string &workgroup, RefDomains &doms) {
    Translation t;
    t.sid = sid;
    if (sid.size() < 8) return t;
    uint64_t auth = sidAuth(sid);
    std::vector<uint32_t> subs = sidSubs(sid);
    for (const WellKnown &w : wellKnown())
        if (w.auth == auth && w.subs == subs) {
            t.type = w.type;
            t.name = w.name;
            t.dom = doms.add(w.domain, sidDomain(sid));
            return t;
        }
#ifdef SHAREDD_UID_R
    uid_r_t id;
    memset(&id, 0, sizeof id);
    bool group = false, have = false;
    if (auth == 22 && subs.size() == 2 && (subs[0] == 1 || subs[0] == 2)) {
        id = uid_r_legacy(subs[1]);
        group = subs[0] == 2;
        have = true;
    } else if (auth == 22 && subs.size() == 5 && subs[0] >= 3 && subs[0] <= 6) {
        id.type = subs[0] <= 4 ? UID_R_UUID : UID_R_GUID;
        for (int i = 0; i < 4; ++i)
            for (int k = 0; k < 4; ++k) id.uuid[4 * i + k] = (uint8_t)(subs[1 + i] >> (24 - 8 * k));
        group = subs[0] == 4 || subs[0] == 6;
        have = true;
    } else if (auth == 5 && subs.size() >= 2 && subs[0] == 21) {
        // a domain account: its id from adconnect, its name from the user db
        std::string reply;
        std::string text = sidText(sid.data(), sid.size());
        if (!text.empty() && adconnectQuery("IDOF " + text, reply, 2)) {
            std::vector<std::string> f = firstRecord(reply);
            if (f.size() == 4 && f[0] == "S" && uid_r_from_str(f[2].c_str(), &id) == 0) {
                group = f[1] == "g";
                std::string name = idName(id, group);
                if (!name.empty()) {
                    t.type = group ? SidGroup : SidUser;
                    t.name = name;
                    t.dom = doms.add(workgroup, sidDomain(sid));
                }
            }
        }
        return t;
    }
    if (have) {
        std::string name = idName(id, group);
        if (!name.empty()) {
            t.type = group ? SidGroup : SidUser;
            t.name = name;
            t.dom = doms.add(group ? UnixGroup : UnixUser, sidDomain(sid));
        }
    }
#else
    (void)workgroup;
#endif
    return t;
}

bool same(const std::string &a, const std::string &b) {
    return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), [](char x, char y) {
               return tolower((unsigned char)x) == tolower((unsigned char)y);
           });
}

Translation translateName(const std::string &full, const std::string &workgroup, RefDomains &doms) {
    Translation t;
    std::string dom, name = full;
    size_t bs = full.find('\\');
    if (bs != std::string::npos) {
        dom = full.substr(0, bs);
        name = full.substr(bs + 1);
    }
    for (const WellKnown &w : wellKnown())
        if (same(name, w.name) && (dom.empty() || same(dom, w.domain))) {
            t.type = w.type;
            t.name = w.name;
            t.sid = makeSid(w.auth, w.subs);
            t.dom = doms.add(w.domain, sidDomain(t.sid));
            return t;
        }
#ifdef SHAREDD_UID_R
    // "Unix User\x" only users, "Unix Group\x" only groups
    bool users = dom.empty() || !same(dom, UnixGroup), groups = dom.empty() || !same(dom, UnixUser);
    for (int pass = 0; pass < 2; ++pass) {
        bool group = pass == 1;
        if (group ? !groups : !users) continue;
        uid_r_t id;
        if (group) {
            struct group_id_r *gr = getgrnam_id_r(name.c_str());
            if (!gr) continue;
            id = gr->gr_gid;
        } else {
            struct passwd_id_r *pw = getpwnam_id_r(name.c_str());
            if (!pw) continue;
            id = pw->pw_uid;
        }
        Sid os = objectSid(id, group);
        t.type = group ? SidGroup : SidUser;
        t.name = name;
        t.sid = os.empty() ? nativeSid(id, group) : os;
        t.dom = doms.add(os.empty() ? (group ? UnixGroup : UnixUser) : workgroup, sidDomain(t.sid));
        return t;
    }
#else
    (void)workgroup;
#endif
    return t;
}

void pushRefDomains(NdrOut &o, const RefDomains &doms) {
    o.ptr(true);
    o.u32((uint32_t)doms.list.size());
    o.ptr(!doms.list.empty());
    o.u32(32);  // max_size
    if (doms.list.empty()) return;
    o.u32((uint32_t)doms.list.size());
    for (auto &d : doms.list) {
        o.lsaString(d.first, true);
        o.ptr(true);
    }
    for (auto &d : doms.list) {
        o.lsaBuffer(d.first, true);
        o.sid(d.second);
    }
}

uint32_t mappedStatus(const std::vector<Translation> &t, uint32_t &mapped) {
    mapped = 0;
    for (auto &x : t)
        if (x.type != SidUnknown) ++mapped;
    return mapped == t.size() ? 0 : mapped ? StSomeNotMapped : StNoneMapped;
}

} // namespace

std::string RpcPipes::hostName() const {
@@ -236,6 +487,7 @@ uint32_t RpcPipes::open(uint32_t handle, uint32_t method) {
    Pipe p;
    if (method & MethodSrvsvc) p.iface = Iface::Srvsvc;
    else if (method & MethodWkssvc) p.iface = Iface::Wkssvc;
    else if (method & MethodLsarpc) p.iface = Iface::Lsarpc;
    _pipes[handle] = p;
    return p.iface == Iface::None ? RpcNotImplemented : RpcOk;
}
@@ -312,7 +564,7 @@ void RpcPipes::bind(Pipe &p, const uint8_t *d, size_t len, bool alter) {
    uint32_t callId = le32(d + 12);
    p.maxRecv = std::min<uint16_t>(maxRecv ? maxRecv : MaxFrag, MaxFrag);
    if (!p.assocGroup) p.assocGroup = assoc ? assoc : 0x53f0;
    const Syntax &want = p.iface == Iface::Srvsvc ? Srvsvc : Wkssvc;
    const Syntax &want = p.iface == Iface::Srvsvc ? Srvsvc : p.iface == Iface::Lsarpc ? Lsarpc : Wkssvc;

    Bytes b;
    header(b, alter ? PtypeAlterResp : PtypeBindAck, FlagFirst | FlagLast, callId);
@@ -322,7 +574,8 @@ void RpcPipes::bind(Pipe &p, const uint8_t *d, size_t len, bool alter) {
    if (alter) {
        put16(b, 0);
    } else {
        std::string port = p.iface == Iface::Srvsvc ? "\\PIPE\\srvsvc" : "\\PIPE\\wkssvc";
        std::string port = p.iface == Iface::Srvsvc ? "\\PIPE\\srvsvc"
                         : p.iface == Iface::Lsarpc ? "\\PIPE\\lsass" : "\\PIPE\\wkssvc";
        put16(b, (uint16_t)(port.size() + 1));
        b.insert(b.end(), port.begin(), port.end());
        b.push_back(0);
@@ -406,6 +659,14 @@ RpcPipes::Bytes RpcPipes::call(Pipe &p, uint16_t opnum, const Bytes &stub, bool
        if (opnum == 21) return serverGetInfo(stub);
    } else if (p.iface == Iface::Wkssvc && opnum == 0) {
        return wkstaGetInfo(stub);
    } else if (p.iface == Iface::Lsarpc) {
        switch (opnum) {
        case 0: return lsaClose();
        case 6:
        case 44: return lsaOpenPolicy();
        case 15: case 57: case 76: return lsaLookupSids(opnum, stub, isFault);
        case 14: case 58: case 68: case 77: return lsaLookupNames(opnum, stub, isFault);
        }
    }
    isFault = true;
    return {};
@@ -572,4 +833,124 @@ RpcPipes::Bytes RpcPipes::wkstaGetInfo(const Bytes &in) {
    return w.b;
}


// ---- lsarpc ----

RpcPipes::Bytes RpcPipes::lsaOpenPolicy() {
    // the policy handle: nothing behind it, any non-null value does
    NdrOut o;
    o.u32(0);
    static uint32_t serial = 0;
    o.u32(++serial);
    for (int i = 0; i < 3; ++i) o.u32(0x53484152);  // "SHAR"
    o.u32(0);                                       // NT_STATUS_OK
    return o.b;
}

RpcPipes::Bytes RpcPipes::lsaClose() {
    NdrOut o;
    for (int i = 0; i < 5; ++i) o.u32(0);  // the null handle
    o.u32(0);
    return o.b;
}

// LookupSids (15), LookupSids2 (57), LookupSids3 (76, no handle)
RpcPipes::Bytes RpcPipes::lsaLookupSids(uint16_t opnum, const Bytes &stub, bool &isFault) {
    NdrIn in(stub);
    if (opnum != 76) in.skip(20);
    uint32_t num = in.u32();
    std::vector<Sid> sids;
    if (in.u32()) {
        uint32_t max = in.u32();
        if (max != num || max > 1000) in.ok = false;
        std::vector<uint32_t> ptrs;
        for (uint32_t i = 0; in.ok && i < max; ++i) ptrs.push_back(in.u32());
        for (uint32_t p : ptrs) sids.push_back(p ? in.sid() : Sid());
    }
    in.u32();                          // [in,out] names: count
    if (in.u32()) in.ok = false;       //   and none in the request
    if (!in.ok) {
        isFault = true;
        return {};
    }

    RefDomains doms;
    std::vector<Translation> out;
    for (const Sid &s : sids) out.push_back(translateSid(s, _workgroup, doms));

    NdrOut o;
    pushRefDomains(o, doms);
    o.u32((uint32_t)out.size());
    o.ptr(!out.empty());
    if (!out.empty()) {
        o.u32((uint32_t)out.size());
        for (auto &t : out) {
            o.u16(t.type);
            o.align(4);
            o.lsaString(t.name, false);
            o.u32(t.dom < 0 ? 0xffffffffu : (uint32_t)t.dom);
            if (opnum != 15) o.u32(0);  // lsa_TranslatedName2: flags
        }
        for (auto &t : out) o.lsaBuffer(t.name, false);
    }
    uint32_t mapped, status = mappedStatus(out, mapped);
    o.u32(mapped);
    o.u32(status);
    return o.b;
}

// LookupNames (14), LookupNames2 (58), LookupNames3 (68), LookupNames4 (77, no handle)
RpcPipes::Bytes RpcPipes::lsaLookupNames(uint16_t opnum, const Bytes &stub, bool &isFault) {
    NdrIn in(stub);
    if (opnum != 77) in.skip(20);
    uint32_t num = in.u32();
    uint32_t max = in.u32();
    if (max != num || max > 1000) in.ok = false;
    std::vector<uint32_t> ptrs;
    for (uint32_t i = 0; in.ok && i < num; ++i) {
        in.u16();
        in.u16();
        ptrs.push_back(in.u32());
    }
    std::vector<std::string> names;
    for (uint32_t p : ptrs) {
        // a conformant varying string without NUL
        names.push_back(p ? in.str() : std::string());
    }
    in.u32();
    if (in.u32()) in.ok = false;
    if (!in.ok) {
        isFault = true;
        return {};
    }

    RefDomains doms;
    std::vector<Translation> out;
    for (const std::string &n : names) out.push_back(translateName(n, _workgroup, doms));

    bool v3 = opnum == 68 || opnum == 77;
    NdrOut o;
    pushRefDomains(o, doms);
    o.u32((uint32_t)out.size());
    o.ptr(!out.empty());
    if (!out.empty()) {
        o.u32((uint32_t)out.size());
        for (auto &t : out) {
            o.u16(t.type);
            o.align(4);
            if (v3) o.ptr(t.type != SidUnknown);  // lsa_TranslatedSid3: the whole SID
            else o.u32(t.type == SidUnknown || t.sid.size() < 12 ? 0xffffffffu : le32(&t.sid[t.sid.size() - 4]));
            o.u32(t.dom < 0 ? 0xffffffffu : (uint32_t)t.dom);
            if (opnum != 14) o.u32(0);
        }
        if (v3)
            for (auto &t : out)
                if (t.type != SidUnknown) o.sid(t.sid);
    }
    uint32_t mapped, status = mappedStatus(out, mapped);
    o.u32(mapped);
    o.u32(status);
    return o.b;
}

} // namespace sharedd
+6 −1
Changes for src/rpc.h: 6 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -73,7 +73,7 @@ public:
    uint32_t transceive(uint32_t handle, const uint8_t *data, size_t len, Bytes &out);

private:
    enum class Iface { None, Srvsvc, Wkssvc };
    enum class Iface { None, Srvsvc, Wkssvc, Lsarpc };
    struct Pipe {
        Iface iface = Iface::None;
        uint16_t maxRecv = 4280;   // the client's fragment size
@@ -93,6 +93,11 @@ private:
    Bytes shareGetInfo(const Bytes &in);
    Bytes serverGetInfo(const Bytes &in);
    Bytes wkstaGetInfo(const Bytes &in);
    // lsarpc: names for the SIDs of security descriptors and back
    Bytes lsaOpenPolicy();
    Bytes lsaClose();
    Bytes lsaLookupSids(uint16_t opnum, const Bytes &in, bool &fault);
    Bytes lsaLookupNames(uint16_t opnum, const Bytes &in, bool &fault);

    std::string hostName() const;