Commit 2d6bc9b9 authored by jan.koester's avatar jan.koester
Browse files

test

parent c1a4314d
Loading
Loading
Loading
Loading
Loading
+24 −0
Changes for data/admin.html: 24 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -70,6 +70,7 @@
                <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-winscripts" style="display:none"><a href="#" onclick="loadWinScripts(); return false;">Windows-Skripte</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>
@@ -348,6 +349,13 @@ function editUserForm(uid) {
        if (data.fields) {
            for (var i = 0; i < data.fields.length; i++) {
                var f = data.fields[i];
                if (f.name === 'logonscript') {
                    html += '<tr><td><label>Anmeldeskript:</label></td><td><select name="logonscript" id="eu_logonscript">'
                         + '<option value="">(keins)</option>'
                         + (f.value ? '<option selected value="' + escapeHtml(f.value) + '">' + escapeHtml(f.value) + '</option>' : '')
                         + '</select> <span class="hint">aus NETLOGON (Menü Windows-Skripte)</span></td></tr>';
                    continue;
                }
                html += '<tr><td><label>' + f.name + ':</label></td>';
                html += '<td><input type="text" name="' + f.name + '" value="' + (f.value || '') + '"></td></tr>';
            }
@@ -361,6 +369,21 @@ function editUserForm(uid) {
        html += '<tr><td></td><td><input type="button" value="Save" onclick="submitEditUser(\'' + uid + '\')"></td></tr>';
        html += '</table></form>';
        setContent(html);
        /* fill the logon script choices (real domains only) */
        var sel = document.getElementById('eu_logonscript');
        if (sel && currentDomain !== 'admin' && typeof dirApi === 'function') {
            ajaxGet(dirApi("winscriptlist"), function(err2, d2) {
                if (err2 || !d2 || !d2.scripts) return;
                var cur = sel.value;
                d2.scripts.forEach(function(sc) {
                    var v = sc.name.replace(/\//g, '\\');
                    if (v.toLowerCase() === cur.toLowerCase()) return;
                    var o = document.createElement('option');
                    o.value = v; o.textContent = v;
                    sel.appendChild(o);
                });
            });
        }
    });
}

@@ -1504,6 +1527,7 @@ window.onload = function() {
        if (data.superadmin || gpo[GPO_MANAGE_SERVICES]) {
            document.getElementById('nav-dns').style.display = '';
            document.getElementById('nav-wingpo').style.display = '';
            document.getElementById('nav-winscripts').style.display = '';
        }
        if (data.superadmin || gpo[GPO_EXPORT_IMPORT])
            document.getElementById('nav-export').style.display = '';
+135 −0
Changes for data/dirviews.js: 135 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -345,3 +345,138 @@ function saveRegistry() {
        loadWinGpoRegistry(g_regGpo, g_regPart);
    });
}

/* ================= Windows logon scripts (NETLOGON) ================= */
var g_scripts = [], g_scriptUsers = [], g_scriptEdit = null;

function loadWinScripts() {
    currentView = loadWinScripts;
    setActiveNav('nav-winscripts');
    loadDomainSelector(function() {
        if (needRealDomain('Windows-Skripte')) return;
        ajaxGet(dirApi("winscriptlist"), function(err, data) {
            if (err) { showError(dirErr(err)); return; }
            g_scripts = data.scripts || [];
            g_scriptUsers = data.users || [];
            var h = '<h3>Windows-Anmeldeskripte</h3>';
            h += '<p class="hint">Skripte im NETLOGON-Share (\\\\' + escapeHtml(currentDomainName()) + '\\NETLOGON). Sie liegen im Verzeichnis der Domäne und werden mit dem Cluster repliziert. Windows führt das einem Benutzer zugewiesene Skript bei der Anmeldung aus.</p>';
            h += '<table class="admin-table userlist"><tr><th>Name</th><th>Größe</th><th>Geändert</th><th>Benutzer</th><th>Actions</th></tr>';
            for (var i = 0; i < g_scripts.length; i++) {
                var sc = g_scripts[i];
                h += '<tr><td>' + escapeHtml(sc.name) + '</td><td>' + sc.size + ' B</td>';
                h += '<td>' + (sc.modified ? new Date(sc.modified * 1000).toLocaleString() : '') + '</td>';
                h += '<td>' + (sc.users.length ? escapeHtml(sc.users.join(', ')) : '<em>-</em>') + '</td><td><ul>';
                h += '<li><a class="button" href="#" onclick="editWinScript(' + i + '); return false;">Bearbeiten</a></li>';
                h += '<li><a class="button" href="#" onclick="assignWinScript(' + i + '); return false;">Zuweisen</a></li>';
                h += '<li><a class="button danger" href="#" onclick="deleteWinScript(' + i + '); return false;">Delete</a></li>';
                h += '</ul></td></tr>';
            }
            if (!g_scripts.length) h += '<tr><td colspan="5"><em>keine Skripte</em></td></tr>';
            h += '</table>';
            h += '<h4>Neues Skript</h4><form onsubmit="newWinScript(this); return false;"><label>Name <input name="name" placeholder="logon.bat"></label> <input type="submit" class="button" value="Anlegen"></form>';
            h += '<h4>Hochladen</h4><form onsubmit="uploadWinScript(this); return false;"><input type="file" name="file"> <label>als <input name="name" placeholder="(Dateiname)"></label> <input type="submit" class="button" value="Hochladen"></form>';
            setContent(h);
        });
    }, true);
}

function currentDomainName() {
    var sel = document.getElementById('domainselect');
    if (sel && sel.selectedIndex >= 0) return sel.options[sel.selectedIndex].text;
    return currentDomain;
}

function newWinScript(f) {
    var name = f.name.value.trim();
    if (!name) return;
    g_scriptEdit = {name: name, content: '@echo off\r\nrem Anmeldeskript\r\n', isNew: true};
    renderWinScriptEditor();
}

function editWinScript(i) {
    var sc = g_scripts[i];
    dirPost("winscriptget", "", {name: sc.name}, function(data) {
        if (!data.text) { alert('Keine Textdatei - nur ersetzen (Hochladen) oder löschen möglich.'); return; }
        g_scriptEdit = {name: data.name, content: data.content, isNew: false};
        renderWinScriptEditor();
    });
}

function renderWinScriptEditor() {
    var e = g_scriptEdit;
    var h = '<h3>' + escapeHtml(e.name) + (e.isNew ? ' (neu)' : '') + '</h3>';
    h += '<p><a href="#" onclick="loadWinScripts(); return false;">&larr; alle Skripte</a></p>';
    h += '<p class="hint">.bat/.cmd/.vbs/.ps1 werden mit Windows-Zeilenenden (CRLF) gespeichert.</p>';
    h += '<textarea id="winscript-content" rows="24" style="width:100%;font-family:monospace" spellcheck="false">' + escapeHtml(e.content.replace(/\r\n/g, '\n')) + '</textarea>';
    h += '<p><a class="button" href="#" onclick="saveWinScript(); return false;">Speichern</a></p>';
    setContent(h);
}

function saveWinScript() {
    var e = g_scriptEdit;
    var content = document.getElementById('winscript-content').value;
    dirPost("winscriptsave", "", {name: e.name, content: content}, function() {
        alert('Gespeichert.');
        loadWinScripts();
    });
}

function uploadWinScript(f) {
    var file = f.file.files[0];
    if (!file) return;
    var name = f.name.value.trim() || file.name;
    var reader = new FileReader();
    reader.onload = function() {
        var fd = new FormData();
        fd.append('name', name);
        fd.append('content', new Blob([reader.result]));
        ajaxPostMultipart(dirApi("winscriptupload", ""), fd, function(err, data) {
            if (err) { alert(dirErr(err)); return; }
            if (data && data.error) { alert(data.error); return; }
            loadWinScripts();
        });
    };
    reader.readAsArrayBuffer(file);
}

function deleteWinScript(i) {
    var sc = g_scripts[i];
    var msg = 'Skript "' + sc.name + '" löschen?';
    if (sc.users.length) msg += '\nEs ist noch ' + sc.users.length + ' Benutzer(n) zugewiesen.';
    if (!confirm(msg)) return;
    dirPost("winscriptdelete", "", {name: sc.name}, function() { loadWinScripts(); });
}

function assignWinScript(i) {
    var sc = g_scripts[i];
    var h = '<h3>' + escapeHtml(sc.name) + ' &ndash; Benutzern zuweisen</h3>';
    h += '<p><a href="#" onclick="loadWinScripts(); return false;">&larr; alle Skripte</a></p>';
    h += '<p class="hint">Angehakte Benutzer bekommen dieses Anmeldeskript; ein Benutzer hat höchstens eines. Abgehakte, die es bisher hatten, verlieren es.</p>';
    h += '<form id="assignform"><table class="admin-table userlist"><tr><th></th><th>Benutzer</th><th>aktuelles Skript</th></tr>';
    for (var k = 0; k < g_scriptUsers.length; k++) {
        var u = g_scriptUsers[k];
        var has = sc.users.indexOf(u.name) >= 0;
        h += '<tr><td><input type="checkbox" value="' + escapeHtml(u.name) + '"' + (has ? ' checked' : '') + '></td>';
        h += '<td>' + escapeHtml(u.name) + '</td><td>' + (u.script ? escapeHtml(u.script) : '<em>-</em>') + '</td></tr>';
    }
    h += '</table></form><p><a class="button" href="#" onclick="saveWinScriptAssign(' + i + '); return false;">Speichern</a></p>';
    setContent(h);
}

function saveWinScriptAssign(i) {
    var sc = g_scripts[i];
    var boxes = document.querySelectorAll('#assignform input[type=checkbox]');
    var add = [], remove = [];
    for (var k = 0; k < boxes.length; k++) {
        var had = sc.users.indexOf(boxes[k].value) >= 0;
        if (boxes[k].checked && !had) add.push(boxes[k].value);
        if (!boxes[k].checked && had) remove.push(boxes[k].value);
    }
    var done = function() { loadWinScripts(); };
    var doRemove = function() {
        if (!remove.length) { done(); return; }
        dirPost("winscriptassign", "", {script: '', users: JSON.stringify(remove)}, done);
    };
    if (add.length) dirPost("winscriptassign", "", {script: sc.name, users: JSON.stringify(add)}, doRemove);
    else doRemove();
}
+4 −4
Changes for docs/ROADMAP.md: 4 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -59,12 +59,12 @@ The directory layer maps them onto AD objects and adds generic objects:
|---|---|---|
| 0 | Rename authdb -> FenrirDirectoryServer (`fenrirds`) | done |
| 1 | Core lib + crypto (MD4/MD5/HMAC-MD5/RC4/AES-CMAC/CFB8/SP800-108) + dsdb | |
| 2 | LDAP: all operations, controls (paged, SD flags, ...), rootDSE, schema entry, SASL GSSAPI/GSS-SPNEGO (Kerberos+NTLM, sign/seal), LDAPS, GC port, CLDAP netlogon ping | bind(simple+SASL GSSAPI)/search/add/modify/delete/modrdn + rootDSE done & wire-tested; SASL GSSAPI(raw)+GSS-SPNEGO binds, sign transport (SSF=1), CLDAP netlogon done. kinit/kvno/CLDAP interop-proven vs MIT+Samba. Paged results (1.2.840.113556.1.4.319, cookie per session, page size capped at 1000), Global Catalog on 3268 (forest-wide, read-only, partial attribute set from the schema GC flag), LDAPS on 636 and GC over TLS on 3269 (server certificate /SSL/CERT + /SSL/KEY) done, tested with Samba ldbsearch. TODO: schema subentry |
| 2 | LDAP: all operations, controls (paged, SD flags, ...), rootDSE, schema entry, SASL GSSAPI/GSS-SPNEGO (Kerberos+NTLM, sign/seal), LDAPS, GC port, CLDAP netlogon ping | bind(simple+SASL GSSAPI)/search/add/modify/delete/modrdn + rootDSE done & wire-tested; SASL GSSAPI(raw)+GSS-SPNEGO binds, sign transport (SSF=1), CLDAP netlogon done. kinit/kvno/CLDAP interop-proven vs MIT+Samba. Paged results (1.2.840.113556.1.4.319, cookie per session, page size capped at 1000), Global Catalog on 3268 (forest-wide, read-only, partial attribute set from the schema GC flag), LDAPS on 636 and GC over TLS on 3269 (server certificate /SSL/CERT + /SSL/KEY) done, tested with Samba ldbsearch. Subschema subentry CN=Aggregate (attributeTypes/objectClasses from the schema tables) done. |
| 3 | LDIF import/export (RFC 2849): library, `fenrirdsctl`, HTTP API, admin UI | |
| 4 | DNS server: AD zones and SRV records, dynamic update (unsigned + GSS-TSIG), forwarding | UDP+TCP server, SRV/A/NS/SOA for domain + _msdcs, generated from directory + configured DC host/IP; wire-tested (dig-style). TODO: directory-backed dnsNode records, dynamic update (RFC2136 + GSS-TSIG), forwarding, AAAA, reverse zones |
| 5 | Kerberos for AD: PAC (logon info, UPN/DNS info, signatures), lookups by sAMAccountName/SPN/UPN, AD salts, RC4-HMAC, kpasswd | KDC on dsdb::Directory (lookups SPN/sAMAccountName($)/UPN, keys from Secrets, krbtgt=TGS key, AD salts). PAC (logon info NDR, client info, UPN/DNS, server+KDC HMAC-SHA1-96 signatures, S-1-22-3 UUID SID in extra SIDs, nested groups) in TGT + service ticket. AS+TGS wire-tested end to end. kpasswd (464 TCP+UDP, RFC 3244 change + set) and "must change at next logon" (pwdLastSet=0 -> KEY_EXPIRED) done, tested with MIT/Heimdal. TODO: PAC verify on TGS, RC4-HMAC preauth, S4U |
| 4 | DNS server: AD zones and SRV records, dynamic update (unsigned + GSS-TSIG), forwarding | Zones/records in the directory (MS-DNSP dnsZone/dnsNode), AD locator records incl. _ldap._tcp.<DomainGUID>.domains._msdcs, RFC 2136 updates unsigned and GSS-TSIG signed (RFC 3645 TKEY + TSIG, tested with BIND nsupdate -g), forwarding, reverse zones, AAAA. |
| 5 | Kerberos for AD: PAC (logon info, UPN/DNS info, signatures), lookups by sAMAccountName/SPN/UPN, AD salts, RC4-HMAC, kpasswd | KDC on dsdb::Directory (lookups SPN/sAMAccountName($)/UPN, keys from Secrets, krbtgt=TGS key, AD salts). PAC (logon info NDR, client info, UPN/DNS, server+KDC HMAC-SHA1-96 signatures, S-1-22-3 UUID SID in extra SIDs, nested groups) in TGT + service ticket. AS+TGS wire-tested end to end. kpasswd (464 TCP+UDP, RFC 3244 change + set) and "must change at next logon" (pwdLastSet=0 -> KEY_EXPIRED) done, tested with MIT/Heimdal. PAC server+KDC signatures verified on TGS; S4U2Self (PA-FOR-USER) and S4U2Proxy (msDS-AllowedToDelegateTo) tested with MIT kvno -U/-P. Not planned: RC4-HMAC (AES only; Windows disables RC4 for Kerberos by default) |
| 6 | NTLM server, DCE/RPC (NDR, auth: NTLMSSP/Kerberos/netlogon schannel), endpoint mapper | DCE/RPC over SMB pipes and TCP (49152), endpoint mapper (135, ept_map), Kerberos/SPNEGO auth DCE style with sealing, netlogon schannel (AES sign+seal); tested with Samba clients |
| 7 | RPC services: lsarpc, samr, netlogon, drsuapi (bind/crack names), srvsvc/wkssvc | netlogon: secure channel, GetCapabilities, GetDomainInfo, ServerPasswordSet2, DsrGetDcName*, NTLM pass-through (LogonSamLogon/Ex/WithFlags, interactive + network, validation 2/3/6) for Windows clients and member servers (proven with adconnect incl. EPM lookup); drsuapi DsBind/DsCrackNames. TODO: srvsvc/wkssvc, SAMR password change fallback |
| 7 | RPC services: lsarpc, samr, netlogon, drsuapi (bind/crack names), srvsvc/wkssvc | netlogon: secure channel, GetCapabilities, GetDomainInfo, ServerPasswordSet2, DsrGetDcName*, NTLM pass-through (LogonSamLogon/Ex/WithFlags, interactive + network, validation 2/3/6) for Windows clients and member servers (proven with adconnect incl. EPM lookup); drsuapi DsBind/DsCrackNames; samr ChangePasswordUser2/3; srvsvc (share enum/info, server info, time) and wkssvc (workstation info), tested with rpcclient/smbclient/Samba python |
| 8 | SMB2/3 server: IPC$ pipes, NETLOGON/SYSVOL shares, signing | |
| 9 | Packaging for FenrirOS (spec, syslite unit, container), admin UI for AD objects/DNS/LDIF | |

+224 −7

File changed.

Preview size limit exceeded, changes collapsed.

+19 −1
Changes for plugin/dns/dns.h: 19 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -41,7 +41,8 @@ namespace fenrirds {
     * kpasswd and the GC, A for the domain and the DC host), which are
     * generated from the configured DC host name and address. Each domain
     * gets its forward zone on first start. Accepts RFC 2136 dynamic updates
     * for zones that allow them (unsigned only for now) and forwards queries
     * for zones that allow them (unsigned, and GSS-TSIG signed for secure
     * zones, RFC 3645) and forwards queries
     * for foreign names to the configured upstream resolvers.
     **************************************************************************/
    class VISIBILITY DnsServer : public Plugin {
@@ -94,6 +95,23 @@ namespace fenrirds {
                                         const std::string &qname, uint16_t qtype,
                                         size_t udpLimit);
        std::vector<uint8_t> handleUpdate(const uint8_t *data, size_t len);
        std::vector<uint8_t> handleUpdateInner(const uint8_t *data, size_t len, bool authenticated);

        /* GSS-TSIG (RFC 3645): TKEY negotiation and TSIG with GSS MICs */
        struct TsigCtx {
            std::vector<uint8_t> key;
            int      etype = 0;
            bool     accSubkey = false;
            uint64_t sendSeq = 0;
            std::string principal;
            time_t   expires = 0;
        };
        std::vector<uint8_t> handleTkey(const uint8_t *data, size_t len, size_t qend);
        /* append a TSIG RR (MAC = GSS MIC) to a response */
        std::vector<uint8_t> signResponse(std::vector<uint8_t> resp, const std::string &keyName,
                                          const std::vector<uint8_t> &requestMac);
        std::mutex                      _TsigMtx;
        std::map<std::string, TsigCtx>  _Tsig;   /* lower-case key name -> context */

        /* forwarding */
        void forwardWorker();
Loading