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

test

parent b7af1ef9
Loading
Loading
Loading
Loading
Loading
+106 −1
Changes for plugin/ad/ad.cpp: 106 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -725,10 +725,25 @@ bool ActiveDirectory::dirAuthenticate(const std::string &domain, const std::stri
    auto e = d->dir->bySamAccountName(sam);
    if (!e) { err = "no such user"; return false; }
    dsdb::Secrets sec = d->dir->secretsOf(e->objectGuid);
    if (!dsdb::verifyPassword(sec, pw)) { err = "wrong password"; return false; }
    if (dsdb::verifyPassword(sec, pw)) {
        outGuid = e->objectGuid;
        return true;
    }
    /* migrated native account: verify the preserved sha512 web hash (no NT
     * hash / clear password was available at migration time) */
    std::string wh = e->str("@webHash");
    if (!wh.empty()) {
        sha512 sha;
        std::string h;
        sha.hash(pw, h);
        if (h == wh) {
            outGuid = e->objectGuid;
            return true;
        }
    }
    err = "wrong password";
    return false;
}

bool ActiveDirectory::dirMemberships(const std::string &domain, const uuid::uuid &guid,
                                     std::vector<uuid::uuid> &groups, std::string &err) {
@@ -832,6 +847,96 @@ bool ActiveDirectory::dirRemoveMember(const std::string &domain, const uuid::uui
    return true;
}

bool ActiveDirectory::dirImportNative(const std::string &domain, int &migrated,
                                      std::string &err) {
    if (g_Replication && g_Replication->isSlave()) {
        err = "this node is a replication slave (read-only); make changes on the master";
        return false;
    }
    std::lock_guard<std::mutex> lock(_Lock);
    migrated = 0;
    Dom *d = domainByName(domain);
    if (!d) { err = "unknown domain '" + domain + "'"; return false; }
    AuthBackend *bck = DomainRegistry::instance().getByName(domain, nullptr);
    if (!bck) { err = "no backend for '" + domain + "'"; return false; }

    dsdb::Dn users = d->meta.baseDn.child("CN", "Users");

    /* native UserData -> SID-less directory user, sha512 hash kept in @webHash */
    {
        User user;
        std::vector<uuid::uuid> uids;
        try { user.list(*bck, uids); } catch (...) {}
        for (const auto &uid : uids) {
            class UserData udat(uid);
            try { size_t p = sizeof(AuthHeader); user.info(*bck, udat, p); } catch (...) { continue; }
            std::string sam = udat.getUsername();
            if (sam.empty()) continue;
            if (d->dir->bySamAccountName(sam)) continue;
            dsdb::Dn dn = users.child("CN", sam);
            if (d->dir->byDn(dn)) continue;
            dsdb::AttrMap a;
            a["objectClass"]    = {"top", "person", "organizationalPerson", "user"};
            a["objectCategory"] = {"CN=Person,CN=Schema,CN=Configuration," + d->meta.baseDn.str()};
            a["sAMAccountName"] = {sam};
            a["cn"] = {sam}; a["name"] = {sam};
            a["distinguishedName"] = {dn.str()};
            try { std::string v = udat.getFirstname(); if (!v.empty()) a["givenName"] = {v}; } catch (...) {}
            try { std::string v = udat.getLastname();  if (!v.empty()) a["sn"] = {v}; } catch (...) {}
            try { std::string v = udat.getMail();      if (!v.empty()) a["mail"] = {v}; } catch (...) {}
            try { std::string v = udat.getPwHash();    if (!v.empty()) a["@webHash"] = {v}; } catch (...) {}
            try { d->dir->create(dn, a, nullptr); ++migrated; } catch (...) {}
        }
    }

    /* native GroupData -> SID-less directory group + members (by username) */
    {
        Group group;
        std::vector<uuid::uuid> gids;
        try { group.list(*bck, gids); } catch (...) {}
        for (const auto &gid : gids) {
            class GroupData gdat(gid);
            try { size_t p = sizeof(AuthHeader); group.info(*bck, gdat, p); } catch (...) { continue; }
            std::string gname = gdat.getName();
            if (gname.empty()) continue;
            if (d->dir->bySamAccountName(gname)) continue;
            dsdb::Dn dn = users.child("CN", gname);
            if (d->dir->byDn(dn)) continue;
            dsdb::AttrMap a;
            a["objectClass"]    = {"top", "group"};
            a["objectCategory"] = {"CN=Group,CN=Schema,CN=Configuration," + d->meta.baseDn.str()};
            a["sAMAccountName"] = {gname};
            a["cn"] = {gname}; a["name"] = {gname};
            a["distinguishedName"] = {dn.str()};
            std::vector<uuid::uuid> mems; gdat.getMembers(mems);
            dsdb::Values memberDns;
            for (const auto &muid : mems) {
                class UserData mu(muid); User u;
                try { size_t p = sizeof(AuthHeader); u.info(*bck, mu, p);
                    std::string msam = mu.getUsername();
                    if (!msam.empty()) memberDns.push_back(users.child("CN", msam).str());
                } catch (...) {}
            }
            if (!memberDns.empty()) a["member"] = memberDns;
            try {
                d->dir->create(dn, a, nullptr);
                for (auto &mdn : memberDns) {
                    dsdb::Dn md; if (!dsdb::Dn::parse(mdn, md)) continue;
                    auto me = d->dir->byDn(md); if (!me) continue;
                    dsdb::AttrMap up;
                    dsdb::Values mo = me->get("memberOf") ? *me->get("memberOf") : dsdb::Values{};
                    mo.push_back(dn.str());
                    up["memberOf"] = mo;
                    try { d->dir->modify(me->objectGuid, up, nullptr); } catch (...) {}
                }
                ++migrated;
            } catch (...) {}
        }
    }
    std::cerr << "[AD] dirImportNative domain=" << domain << " migrated=" << migrated << std::endl;
    return true;
}

/******************************************************************************
 * Domain loading: every domain of the registry becomes a provisioned
 * directory naming context.
+2 −0
Changes for plugin/ad/ad.h: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -279,6 +279,8 @@ namespace fenrirds {

        /* unified directory CRUD (see AdPlugin for the contract) */
        void reloadDomains() override { loadDomains(); }
        bool dirImportNative(const std::string &domain, int &migrated,
                             std::string &err) override;
        bool dirList(const std::string &domain, int kind,
                     std::vector<dsdb::Entry> &out, std::string &err) override;
        bool dirGet(const std::string &domain, const uuid::uuid &guid,
+19 −2
Changes for src/admin.cpp: 19 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -2056,18 +2056,35 @@ namespace fenrirds {
        void apiImportDb(libhttppp::HttpRequest &curreq, const char *cdid) {
            AuthBackend *backend = getDomain(cdid);
            if (!backend) throw AuthBackendError("import: could not resolve domain!");
            AuthBackend::Guard guard(*backend);

            std::string reqbody = getRequestBody(curreq);
            json_object *jreq = json_tokener_parse(reqbody.c_str());
            if (!jreq) throw AuthBackendError("import: invalid JSON!");

            int count = Import::fromJson(*backend, jreq);
            int count = 0;
            {
                /* scoped: release the backend write lock before migrating, so
                 * the directory's own locking (below) can't deadlock against it */
                AuthBackend::Guard guard(*backend);
                count = Import::fromJson(*backend, jreq);
            }
            json_object_put(jreq);

            /* Import restores raw native records. For a real domain, fold any
             * native UserData/GroupData into the directory (single source) so
             * imported users/groups show up and can log in. */
            int migrated = 0;
            if (strcmp(cdid, "admin") != 0 && g_AdPlugin) {
                std::string domain = domainNameFor(cdid);
                std::string err;
                if (!domain.empty() && !g_AdPlugin->dirImportNative(domain, migrated, err))
                    std::cerr << "import: dirImportNative failed: " << err << std::endl;
            }

            json_object *jobj = json_object_new_object();
            json_object_object_add(jobj, "status", json_object_new_string("ok"));
            json_object_object_add(jobj, "imported", json_object_new_int(count));
            json_object_object_add(jobj, "migrated", json_object_new_int(migrated));
            sendJson(curreq, jobj);
            json_object_put(jobj);
        }
+29 −2
Changes for src/fenrirds.cpp: 29 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@
#include "fenrirds.h"
#include "user.h"
#include "domain.h"
#include "profile_webp.h"
#include "group.h"
#include "types.h"
#include "hash/sha512.h"
@@ -661,9 +662,34 @@ namespace fenrirds {
            } catch (AuthBackendError &) {
            }

            rep.setState(HTTP404);
            rep.send(curreq,"",0);
            /* directory user (no native record): serve its thumbnailPhoto */
            if (g_AdPlugin && strcmp(domain, "admin") != 0) {
                std::string dnName;
                try {
                    class DomainData ddat(did);
                    Domain dom;
                    size_t dp = sizeof(AuthHeader);
                    dom.info(_AdminBackend, ddat, dp);
                    dnName = ddat.getDomainName();
                } catch (...) {}
                if (!dnName.empty()) {
                    dsdb::Entry e;
                    std::string err;
                    if (g_AdPlugin->dirGet(dnName, uid, e, err)) {
                        std::string tp = e.str("thumbnailPhoto");
                        if (!tp.empty()) {
                            rep.setContentType("image/jpeg");
                            rep.send(curreq, tp.data(), tp.size());
                            return;
                        }
                    }
                }
            }

            /* fallback: the built-in default avatar, so the UI never shows a
             * broken image for accounts without a picture */
            rep.setContentType("image/webp");
            rep.send(curreq, reinterpret_cast<const char*>(profile_webp), sizeof(profile_webp));
        };

        AuthBackend *getDomainBackend(const ::uuid::uuid &did){
@@ -1182,6 +1208,7 @@ int main(int argc,char *argv[]){
        /* Start each plugin in its own background thread (runs as unprivileged user) */
        std::vector<std::thread> pluginThreads;
        for (auto &[pname, lp] : pluginMgr.plugins()) {
            if (!lp.ready) continue;  // its init() failed (see above)
            pluginThreads.emplace_back([&lp, &pname](){
                try {
                    lp.instance->run();
+1 −0
Changes for src/plugin.cpp: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -203,6 +203,7 @@ void PluginManager::initAll() {
        if (lp.instance) {
            try {
                lp.instance->init();
                lp.ready = true;
            } catch (const netplus::NetException &e) {
                std::cerr << "Plugin init failed: " << name << ": "
                          << e.what() << std::endl;
Loading