Loading CMakeLists.txt +1 −0 Changes for CMakeLists.txt: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -89,6 +89,7 @@ install(FILES data/wizzard.html data/setup.html data/serverconfig.js data/dirviews.js data/login.html data/index.html data/fenrirds.png Loading data/admin.html +7 −0 Changes for data/admin.html: 7 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ <title>FenrirDirectoryServer Admin</title> <link rel="stylesheet" href="/style.css"> <script src="/serverconfig.js"></script> <script src="/dirviews.js"></script> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> .admin-table { border-collapse: separate; border-spacing: 0; width: 100%; margin: 1em 0; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; } Loading Loading @@ -68,6 +69,8 @@ <li id="nav-clients"><a href="#" onclick="loadClients(); return false;">Clients</a></li> <li id="nav-services" style="display:none"><a href="#" onclick="loadServices(); return false;">Services</a></li> <li id="nav-gpos"><a href="#" onclick="loadGpos(); return false;">Policies</a></li> <li id="nav-wingpo" style="display:none"><a href="#" onclick="loadWinGpos(); return false;">Windows GPOs</a></li> <li id="nav-dns" style="display:none"><a href="#" onclick="loadDns(); return false;">DNS</a></li> <li id="nav-sessions"><a href="#" onclick="loadSessions(); return false;">Sessions</a></li> <li id="nav-server" style="display:none"><a href="#" onclick="loadServerConfig(); return false;">Server</a></li> <li id="nav-cluster" style="display:none"><a href="#" onclick="loadClusterStatus(); return false;">Cluster</a></li> Loading Loading @@ -1498,6 +1501,10 @@ window.onload = function() { document.getElementById('nav-computers').style.display = ''; if (data.superadmin || gpo[GPO_MANAGE_SERVICES]) document.getElementById('nav-services').style.display = ''; if (data.superadmin || gpo[GPO_MANAGE_SERVICES]) { document.getElementById('nav-dns').style.display = ''; document.getElementById('nav-wingpo').style.display = ''; } if (data.superadmin || gpo[GPO_EXPORT_IMPORT]) document.getElementById('nav-export').style.display = ''; Loading plugin/ad/ad.cpp +5 −0 Changes for plugin/ad/ad.cpp: 5 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -39,6 +39,8 @@ #include <netplus/eventapi.h> #include "ad.h" #include "grouppolicy.h" #include "sysvol.h" #include "crypto/gss.h" #include "crypto/ntlm.h" Loading Loading @@ -1090,6 +1092,9 @@ void ActiveDirectory::loadDomains() { d.dir->provision(_AdminPassword); if (!_DcHost.empty()) d.dir->provisionDc(_DcHost); /* this DC's own account + SPNs */ /* SYSVOL layout + the default Windows GPOs and their links */ sysvol::Store sv(*bck); winpolicy::Manager(*d.dir, sv).ensureDefaults(); } catch (std::exception &ex) { std::cerr << "AD: provision '" << e.name << "' failed: " << ex.what() << std::endl; } Loading plugin/smb/smb.cpp +160 −1 Changes for plugin/smb/smb.cpp: 160 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -625,7 +625,7 @@ SmbServer::Reply SmbServer::sessionSetup(Conn &c, const smb::Smb2Header &req, c.sessions.erase(sid); smb::Smb2Header r2 = req; r2.sessionId = sid; reply.pdu = errorResponse(r2, ST_LOGON_FAILURE); reply.pdu = errorResponse(r2, smb::STATUS_LOGON_FAILURE); return reply; }; /* principal -> user + DNS domain */ Loading Loading @@ -1621,3 +1621,162 @@ std::vector<uint8_t> SmbServer::queryInfo(Conn &c, const smb::Smb2Header &req, u resp.insert(resp.end(), out.begin(), out.end()); return resp; } /***************************************************************************** * SET_INFO *****************************************************************************/ std::vector<uint8_t> SmbServer::setInfo(Conn &c, Session &s, const smb::Smb2Header &req, uint64_t fid, const uint8_t *body, size_t blen) { (void)s; if (blen < 32) return errorResponse(req, ST_INVALID_PARAMETER); uint8_t type = body[2], cls = body[3]; uint32_t len = rd32(body + 4); uint16_t off = rd16(body + 8); if (off < 64 || size_t(off - 64) + len > blen) return errorResponse(req, ST_INVALID_PARAMETER); const uint8_t *b = body + (off - 64); auto it = c.opens.find(fid); if (it == c.opens.end()) return errorResponse(req, ST_FILE_CLOSED); Open &o = it->second; if (o.pipe) return errorResponse(req, ST_NOT_SUPPORTED); bool fixedFolder = o.root || o.path.empty() || (o.kind == SHARE_NETLOGON && o.path == "scripts"); sysvol::Store *st = o.root ? nullptr : storeFor(o.domain); std::string err; auto ok = [&]() { smb::Smb2Header h; h.command = req.command; h.messageId = req.messageId; h.sessionId = req.sessionId; h.treeId = req.treeId; h.status = ST_SUCCESS; h.creditReqResp = std::max<uint16_t>(1, std::min<uint16_t>(req.creditReqResp, 128)); auto resp = smb::buildHeader(h); w16(resp, 2); return resp; }; if (type == 3) { /* security descriptor */ if (!o.writable || !st) return errorResponse(req, ST_ACCESS_DENIED); if (!st->setSecurity(o.path, std::string(reinterpret_cast<const char *>(b), len), err)) return errorResponse(req, ST_ACCESS_DENIED); return ok(); } if (type != 1) return errorResponse(req, ST_INVALID_INFO_CLASS); switch (cls) { case 4: { /* FileBasicInformation */ if (len < 36) return errorResponse(req, ST_INFO_LENGTH_MISMATCH); if (!o.writable || fixedFolder) return ok(); /* times on fixed folders: ignore */ flush(o); int64_t ct = int64_t(rd64(b)), at = int64_t(rd64(b + 8)), mt = int64_t(rd64(b + 16)); uint32_t attrs = rd32(b + 32); if (!st->setInfo(o.path, attrs ? int64_t(attrs & ~uint32_t(sysvol::ATTR_NORMAL)) : -1, ct > 0 ? ct : -1, mt > 0 ? mt : -1, at > 0 ? at : -1, err)) return errorResponse(req, ST_ACCESS_DENIED); return ok(); } case 10: { /* FileRenameInformation */ if (len < 20) return errorResponse(req, ST_INFO_LENGTH_MISMATCH); if (!o.writable || fixedFolder) return errorResponse(req, ST_ACCESS_DENIED); bool replace = b[0] != 0; uint32_t nlen = rd32(b + 16); if (20 + size_t(nlen) > len) return errorResponse(req, ST_INVALID_PARAMETER); std::string target; if (!sysvol::normalize(rpc::fromUtf16(b + 20, nlen), target) || target.empty()) return errorResponse(req, ST_NAME_INVALID); std::string to; if (o.kind == SHARE_SYSVOL) { std::string first = target.substr(0, target.find('/')); if (lowerAscii(first) != lowerAscii(o.domain) || target.find('/') == std::string::npos) return errorResponse(req, ST_ACCESS_DENIED); /* no moves between domains */ to = target.substr(target.find('/') + 1); } else { to = "scripts/" + target; } flush(o); if (!st->rename(o.path, to, replace, err)) { std::cerr << "[SMB] rename " << o.path << " -> " << to << ": " << err << std::endl; return errorResponse(req, err == "target exists" ? ST_NAME_COLLISION : ST_ACCESS_DENIED); } /* opens below the old path follow the move */ for (auto &kv : c.opens) { Open &x = kv.second; if (x.pipe || x.domain != o.domain || x.kind != o.kind) continue; if (lowerAscii(x.path) == lowerAscii(o.path) && &x != &o) x.path = to; else if (lowerAscii(x.path).compare(0, o.path.size() + 1, lowerAscii(o.path) + "/") == 0) x.path = to + x.path.substr(o.path.size()); } o.path = to; return ok(); } case 13: /* FileDispositionInformation */ case 64: { /* FileDispositionInformationEx */ if (len < 1) return errorResponse(req, ST_INFO_LENGTH_MISMATCH); bool del = cls == 13 ? b[0] != 0 : (len >= 4 && (rd32(b) & 1)); if (del) { if (!o.writable || fixedFolder) return errorResponse(req, ST_ACCESS_DENIED); if (o.dir && st && !st->list(o.path).empty()) return errorResponse(req, ST_DIRECTORY_NOT_EMPTY); } o.deleteOnClose = del; return ok(); } case 20: { /* FileEndOfFileInformation */ if (len < 8) return errorResponse(req, ST_INFO_LENGTH_MISMATCH); if (!o.writable || o.dir) return errorResponse(req, ST_ACCESS_DENIED); uint64_t eof = rd64(b); if (eof > (64ULL << 20)) return errorResponse(req, 0xC000007F /* DISK_FULL */); o.data.resize(eof, '\0'); o.dirty = true; return ok(); } case 14: /* FilePositionInformation */ case 16: /* FileModeInformation */ case 19: /* FileAllocationInformation */ return ok(); default: return errorResponse(req, ST_INVALID_INFO_CLASS); } } } /* namespace fenrirds */ /***************************************************************************** * Plugin factory *****************************************************************************/ #include <json-c/json.h> extern "C" { fenrirds::Plugin *fenrirds_plugin_create(fenrirds::AuthBackend &backend, const char *configJson) { std::string bindAddr = "0.0.0.0", host; int port = 445; if (configJson) { json_object *j = json_tokener_parse(configJson); if (j) { if (auto *x = json_object_object_get(j, "smb_bind")) bindAddr = json_object_get_string(x); if (auto *x = json_object_object_get(j, "smb_port")) port = json_object_get_int(x); if (auto *x = json_object_object_get(j, "dns_hostname")) host = json_object_get_string(x); json_object_put(j); } } return new fenrirds::SmbServer(backend, bindAddr, port, host); } void fenrirds_plugin_destroy(fenrirds::Plugin *p) { delete p; } } src/CMakeLists.txt +1 −0 Changes for src/CMakeLists.txt: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ list(APPEND fenrirdsSrcs dsdb/dns.cpp dsdb/secdesc.cpp sysvol.cpp grouppolicy.cpp rpc/ndr.cpp rpc/dcerpc.cpp rpc/services.cpp Loading Loading
CMakeLists.txt +1 −0 Changes for CMakeLists.txt: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -89,6 +89,7 @@ install(FILES data/wizzard.html data/setup.html data/serverconfig.js data/dirviews.js data/login.html data/index.html data/fenrirds.png Loading
data/admin.html +7 −0 Changes for data/admin.html: 7 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ <title>FenrirDirectoryServer Admin</title> <link rel="stylesheet" href="/style.css"> <script src="/serverconfig.js"></script> <script src="/dirviews.js"></script> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> .admin-table { border-collapse: separate; border-spacing: 0; width: 100%; margin: 1em 0; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; } Loading Loading @@ -68,6 +69,8 @@ <li id="nav-clients"><a href="#" onclick="loadClients(); return false;">Clients</a></li> <li id="nav-services" style="display:none"><a href="#" onclick="loadServices(); return false;">Services</a></li> <li id="nav-gpos"><a href="#" onclick="loadGpos(); return false;">Policies</a></li> <li id="nav-wingpo" style="display:none"><a href="#" onclick="loadWinGpos(); return false;">Windows GPOs</a></li> <li id="nav-dns" style="display:none"><a href="#" onclick="loadDns(); return false;">DNS</a></li> <li id="nav-sessions"><a href="#" onclick="loadSessions(); return false;">Sessions</a></li> <li id="nav-server" style="display:none"><a href="#" onclick="loadServerConfig(); return false;">Server</a></li> <li id="nav-cluster" style="display:none"><a href="#" onclick="loadClusterStatus(); return false;">Cluster</a></li> Loading Loading @@ -1498,6 +1501,10 @@ window.onload = function() { document.getElementById('nav-computers').style.display = ''; if (data.superadmin || gpo[GPO_MANAGE_SERVICES]) document.getElementById('nav-services').style.display = ''; if (data.superadmin || gpo[GPO_MANAGE_SERVICES]) { document.getElementById('nav-dns').style.display = ''; document.getElementById('nav-wingpo').style.display = ''; } if (data.superadmin || gpo[GPO_EXPORT_IMPORT]) document.getElementById('nav-export').style.display = ''; Loading
plugin/ad/ad.cpp +5 −0 Changes for plugin/ad/ad.cpp: 5 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -39,6 +39,8 @@ #include <netplus/eventapi.h> #include "ad.h" #include "grouppolicy.h" #include "sysvol.h" #include "crypto/gss.h" #include "crypto/ntlm.h" Loading Loading @@ -1090,6 +1092,9 @@ void ActiveDirectory::loadDomains() { d.dir->provision(_AdminPassword); if (!_DcHost.empty()) d.dir->provisionDc(_DcHost); /* this DC's own account + SPNs */ /* SYSVOL layout + the default Windows GPOs and their links */ sysvol::Store sv(*bck); winpolicy::Manager(*d.dir, sv).ensureDefaults(); } catch (std::exception &ex) { std::cerr << "AD: provision '" << e.name << "' failed: " << ex.what() << std::endl; } Loading
plugin/smb/smb.cpp +160 −1 Changes for plugin/smb/smb.cpp: 160 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -625,7 +625,7 @@ SmbServer::Reply SmbServer::sessionSetup(Conn &c, const smb::Smb2Header &req, c.sessions.erase(sid); smb::Smb2Header r2 = req; r2.sessionId = sid; reply.pdu = errorResponse(r2, ST_LOGON_FAILURE); reply.pdu = errorResponse(r2, smb::STATUS_LOGON_FAILURE); return reply; }; /* principal -> user + DNS domain */ Loading Loading @@ -1621,3 +1621,162 @@ std::vector<uint8_t> SmbServer::queryInfo(Conn &c, const smb::Smb2Header &req, u resp.insert(resp.end(), out.begin(), out.end()); return resp; } /***************************************************************************** * SET_INFO *****************************************************************************/ std::vector<uint8_t> SmbServer::setInfo(Conn &c, Session &s, const smb::Smb2Header &req, uint64_t fid, const uint8_t *body, size_t blen) { (void)s; if (blen < 32) return errorResponse(req, ST_INVALID_PARAMETER); uint8_t type = body[2], cls = body[3]; uint32_t len = rd32(body + 4); uint16_t off = rd16(body + 8); if (off < 64 || size_t(off - 64) + len > blen) return errorResponse(req, ST_INVALID_PARAMETER); const uint8_t *b = body + (off - 64); auto it = c.opens.find(fid); if (it == c.opens.end()) return errorResponse(req, ST_FILE_CLOSED); Open &o = it->second; if (o.pipe) return errorResponse(req, ST_NOT_SUPPORTED); bool fixedFolder = o.root || o.path.empty() || (o.kind == SHARE_NETLOGON && o.path == "scripts"); sysvol::Store *st = o.root ? nullptr : storeFor(o.domain); std::string err; auto ok = [&]() { smb::Smb2Header h; h.command = req.command; h.messageId = req.messageId; h.sessionId = req.sessionId; h.treeId = req.treeId; h.status = ST_SUCCESS; h.creditReqResp = std::max<uint16_t>(1, std::min<uint16_t>(req.creditReqResp, 128)); auto resp = smb::buildHeader(h); w16(resp, 2); return resp; }; if (type == 3) { /* security descriptor */ if (!o.writable || !st) return errorResponse(req, ST_ACCESS_DENIED); if (!st->setSecurity(o.path, std::string(reinterpret_cast<const char *>(b), len), err)) return errorResponse(req, ST_ACCESS_DENIED); return ok(); } if (type != 1) return errorResponse(req, ST_INVALID_INFO_CLASS); switch (cls) { case 4: { /* FileBasicInformation */ if (len < 36) return errorResponse(req, ST_INFO_LENGTH_MISMATCH); if (!o.writable || fixedFolder) return ok(); /* times on fixed folders: ignore */ flush(o); int64_t ct = int64_t(rd64(b)), at = int64_t(rd64(b + 8)), mt = int64_t(rd64(b + 16)); uint32_t attrs = rd32(b + 32); if (!st->setInfo(o.path, attrs ? int64_t(attrs & ~uint32_t(sysvol::ATTR_NORMAL)) : -1, ct > 0 ? ct : -1, mt > 0 ? mt : -1, at > 0 ? at : -1, err)) return errorResponse(req, ST_ACCESS_DENIED); return ok(); } case 10: { /* FileRenameInformation */ if (len < 20) return errorResponse(req, ST_INFO_LENGTH_MISMATCH); if (!o.writable || fixedFolder) return errorResponse(req, ST_ACCESS_DENIED); bool replace = b[0] != 0; uint32_t nlen = rd32(b + 16); if (20 + size_t(nlen) > len) return errorResponse(req, ST_INVALID_PARAMETER); std::string target; if (!sysvol::normalize(rpc::fromUtf16(b + 20, nlen), target) || target.empty()) return errorResponse(req, ST_NAME_INVALID); std::string to; if (o.kind == SHARE_SYSVOL) { std::string first = target.substr(0, target.find('/')); if (lowerAscii(first) != lowerAscii(o.domain) || target.find('/') == std::string::npos) return errorResponse(req, ST_ACCESS_DENIED); /* no moves between domains */ to = target.substr(target.find('/') + 1); } else { to = "scripts/" + target; } flush(o); if (!st->rename(o.path, to, replace, err)) { std::cerr << "[SMB] rename " << o.path << " -> " << to << ": " << err << std::endl; return errorResponse(req, err == "target exists" ? ST_NAME_COLLISION : ST_ACCESS_DENIED); } /* opens below the old path follow the move */ for (auto &kv : c.opens) { Open &x = kv.second; if (x.pipe || x.domain != o.domain || x.kind != o.kind) continue; if (lowerAscii(x.path) == lowerAscii(o.path) && &x != &o) x.path = to; else if (lowerAscii(x.path).compare(0, o.path.size() + 1, lowerAscii(o.path) + "/") == 0) x.path = to + x.path.substr(o.path.size()); } o.path = to; return ok(); } case 13: /* FileDispositionInformation */ case 64: { /* FileDispositionInformationEx */ if (len < 1) return errorResponse(req, ST_INFO_LENGTH_MISMATCH); bool del = cls == 13 ? b[0] != 0 : (len >= 4 && (rd32(b) & 1)); if (del) { if (!o.writable || fixedFolder) return errorResponse(req, ST_ACCESS_DENIED); if (o.dir && st && !st->list(o.path).empty()) return errorResponse(req, ST_DIRECTORY_NOT_EMPTY); } o.deleteOnClose = del; return ok(); } case 20: { /* FileEndOfFileInformation */ if (len < 8) return errorResponse(req, ST_INFO_LENGTH_MISMATCH); if (!o.writable || o.dir) return errorResponse(req, ST_ACCESS_DENIED); uint64_t eof = rd64(b); if (eof > (64ULL << 20)) return errorResponse(req, 0xC000007F /* DISK_FULL */); o.data.resize(eof, '\0'); o.dirty = true; return ok(); } case 14: /* FilePositionInformation */ case 16: /* FileModeInformation */ case 19: /* FileAllocationInformation */ return ok(); default: return errorResponse(req, ST_INVALID_INFO_CLASS); } } } /* namespace fenrirds */ /***************************************************************************** * Plugin factory *****************************************************************************/ #include <json-c/json.h> extern "C" { fenrirds::Plugin *fenrirds_plugin_create(fenrirds::AuthBackend &backend, const char *configJson) { std::string bindAddr = "0.0.0.0", host; int port = 445; if (configJson) { json_object *j = json_tokener_parse(configJson); if (j) { if (auto *x = json_object_object_get(j, "smb_bind")) bindAddr = json_object_get_string(x); if (auto *x = json_object_object_get(j, "smb_port")) port = json_object_get_int(x); if (auto *x = json_object_object_get(j, "dns_hostname")) host = json_object_get_string(x); json_object_put(j); } } return new fenrirds::SmbServer(backend, bindAddr, port, host); } void fenrirds_plugin_destroy(fenrirds::Plugin *p) { delete p; } }
src/CMakeLists.txt +1 −0 Changes for src/CMakeLists.txt: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ list(APPEND fenrirdsSrcs dsdb/dns.cpp dsdb/secdesc.cpp sysvol.cpp grouppolicy.cpp rpc/ndr.cpp rpc/dcerpc.cpp rpc/services.cpp Loading