Loading plugin/ad/ad.cpp +89 −24 Changes for plugin/ad/ad.cpp: 89 added lines, 24 removed lines. Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ #include <iostream> #include <cstring> #include <map> #include <algorithm> #include <sstream> #include <memory> Loading Loading @@ -474,6 +475,7 @@ void ActiveDirectory::CreateConnection(std::shared_ptr<netplus::con> &res) { } void ActiveDirectory::ConnectEvent(netplus::con &curcon, const int, ULONG_PTR) { std::cerr << "[LDAP] TCP connect" << std::endl; std::lock_guard<std::mutex> lock(_Lock); _Sessions[&curcon] = LdapSession(); } Loading @@ -488,6 +490,9 @@ void ActiveDirectory::ResponseEvent(netplus::con &, const int, ULONG_PTR) {} void ActiveDirectory::RequestEvent(netplus::con &curcon, const int, ULONG_PTR) { if (curcon.RecvData.empty()) return; std::cerr << "[LDAP] recv " << curcon.RecvData.size() << "B (first=0x" << std::hex << (curcon.RecvData.empty() ? 0 : (unsigned char)curcon.RecvData[0]) << std::dec << ")" << std::endl; LdapSession *session = nullptr; { Loading Loading @@ -656,8 +661,11 @@ std::vector<uint8_t> ActiveDirectory::handleBindRequest(const uint8_t *content, session.bindDN.clear(); return buildLdapMessage(msgId, appResp, buildLdapResult(LDAP_SUCCESS, "", "")); } if (authenticateSimple(bindDN, password, session)) if (authenticateSimple(bindDN, password, session)) { std::cerr << "[LDAP] bind SIMPLE dn='" << bindDN << "' -> SUCCESS" << std::endl; return buildLdapMessage(msgId, appResp, buildLdapResult(LDAP_SUCCESS, "", "")); } std::cerr << "[LDAP] bind SIMPLE dn='" << bindDN << "' -> INVALID_CREDENTIALS" << std::endl; return buildLdapMessage(msgId, appResp, buildLdapResult(LDAP_INVALID_CREDENTIALS, "", "80090308: LdapErr: DSID-0C090447, data 52e")); Loading Loading @@ -693,8 +701,11 @@ std::vector<uint8_t> ActiveDirectory::handleBindRequest(const uint8_t *content, namespace gss = fenrirds::crypto::gss; std::string mech = toLower(mechanism); bool spnego = (mech == "gss-spnego"); std::cerr << "[LDAP] bind SASL mech='" << mechanism << "' gssState=" << int(session.gssState) << " inTok=" << inTok.size() << "B" << std::endl; if (mech != "gssapi" && !spnego) { session.gssState = LdapSession::GssNone; std::cerr << "[LDAP] bind SASL -> AUTH_METHOD_NOT_SUPPORTED (mech)" << std::endl; return bindResp(LDAP_AUTH_METHOD_NOT_SUPPORTED, nullptr); } if (!g_KdcPlugin) Loading @@ -716,8 +727,13 @@ std::vector<uint8_t> ActiveDirectory::handleBindRequest(const uint8_t *content, return bindResp(LDAP_INVALID_CREDENTIALS, nullptr); } GssAcceptResult res; if (!g_KdcPlugin->gssAccept(apReq, res)) if (!g_KdcPlugin->gssAccept(apReq, res)) { std::cerr << "[LDAP] bind SASL gssAccept FAILED (apReq=" << apReq.size() << "B) -> INVALID_CREDENTIALS" << std::endl; return bindResp(LDAP_INVALID_CREDENTIALS, nullptr); } std::cerr << "[LDAP] bind SASL gssAccept OK client='" << res.clientPrincipal << "' etype=" << res.sessionEtype << std::endl; session.gssKey = res.sessionKey; session.gssEtype = res.sessionEtype; session.gssClient = res.clientPrincipal; Loading Loading @@ -761,6 +777,8 @@ std::vector<uint8_t> ActiveDirectory::handleBindRequest(const uint8_t *content, session.authenticated = true; session.bindDN = session.gssClient; session.gssState = LdapSession::GssNone; std::cerr << "[LDAP] bind SASL COMPLETE client='" << session.gssClient << "' ssf=" << session.ssf << " -> SUCCESS" << std::endl; return bindResp(LDAP_SUCCESS, nullptr); } Loading Loading @@ -992,18 +1010,31 @@ LdapEntry ActiveDirectory::renderEntry(const dsdb::Entry &ent, * NETLOGON_SAM_LOGON_RESPONSE_EX) so clients can pick this DC. *****************************************************************************/ namespace { void nlName(std::vector<uint8_t> &o, const std::string &name) { size_t i = 0; while (i < name.size()) { size_t dot = name.find('.', i); if (dot == std::string::npos) dot = name.size(); size_t l = dot - i; if (l == 0) break; o.push_back(uint8_t(l)); for (size_t k = i; k < dot; ++k) o.push_back(uint8_t(name[k])); i = dot + 1; } o.push_back(0); /* RFC1035 DNS name with compression, as required by MS-ADTS 6.3.5 for the * netlogon response. Pointers are relative to the start of the blob (= o[0]). * A shared comp map lets DnsDomainName point at DnsForestName, DnsHostName * reuse the "example.com" suffix, repeated site names collapse, etc. */ void nlName(std::vector<uint8_t> &o, const std::string &name, std::map<std::string, uint16_t> &comp) { std::string rem = name; while (!rem.empty()) { auto it = comp.find(rem); if (it != comp.end()) { uint16_t ptr = 0xC000 | it->second; o.push_back(uint8_t(ptr >> 8)); o.push_back(uint8_t(ptr & 0xff)); return; } if (o.size() < 0x4000) comp[rem] = uint16_t(o.size()); size_t dot = rem.find('.'); std::string label = (dot == std::string::npos) ? rem : rem.substr(0, dot); if (label.empty()) break; o.push_back(uint8_t(label.size())); for (char c : label) o.push_back(uint8_t(c)); rem = (dot == std::string::npos) ? std::string() : rem.substr(dot + 1); } o.push_back(0); /* root label terminates an uncompressed (sub)name */ } void nlU16(std::vector<uint8_t> &o, uint16_t v) { o.push_back(v & 0xff); o.push_back(v >> 8); } void nlU32(std::vector<uint8_t> &o, uint32_t v) { for (int i = 0; i < 4; ++i) o.push_back((v >> (8 * i)) & 0xff); } Loading @@ -1019,6 +1050,7 @@ std::vector<uint8_t> ActiveDirectory::buildNetlogonResponse(const Dom &d, uint32 uint32_t flags = 0x1 | 0x4 | 0x8 | 0x10 | 0x20 | 0x40 | 0x80 | 0x100 | 0x200 | 0x20000000u | 0x40000000u | 0x80000000u; /* PDC/GC/LDAP/DS/KDC/TIMESERV/CLOSEST/WRITABLE/GOODTIME + DNS_{CONTROLLER,DOMAIN,FOREST} */ std::map<std::string, uint16_t> nc; /* DNS name compression context (per blob) */ nlU16(b, 23); /* Opcode = LOGON_SAM_LOGON_RESPONSE_EX */ nlU16(b, 0); /* Sbz */ nlU32(b, flags); Loading @@ -1027,15 +1059,29 @@ std::vector<uint8_t> ActiveDirectory::buildNetlogonResponse(const Dom &d, uint32 if (auto head = d.dir->byDn(d.meta.baseDn)) std::memcpy(guid, head->objectGuid.value, 16); b.insert(b.end(), guid, guid + 16); nlName(b, d.meta.dnsName); /* DnsForestName (= domain, single-domain forest) */ nlName(b, d.meta.dnsName); /* DnsDomainName */ nlName(b, host); /* DnsHostName */ nlName(b, d.meta.netbios); /* NetbiosDomainName */ nlName(b, dsdb::upper(comp)); /* NetbiosComputerName */ nlName(b, user); /* UserName (echo / empty) */ nlName(b, site); /* DcSiteName */ nlName(b, site); /* ClientSiteName */ nlU32(b, ntVersion ? ntVersion : 0x00000005); nlName(b, d.meta.dnsName, nc); /* DnsForestName (= domain, single-domain forest) */ nlName(b, d.meta.dnsName, nc); /* DnsDomainName */ nlName(b, host, nc); /* DnsHostName */ nlName(b, d.meta.netbios, nc); /* NetbiosDomainName */ nlName(b, dsdb::upper(comp), nc); /* NetbiosComputerName */ nlName(b, user, nc); /* UserName (echo / empty) */ nlName(b, site, nc); /* DcSiteName */ nlName(b, site, nc); /* ClientSiteName */ /* (DcSockAddr would go here iff NETLOGON_NT_VERSION_5EX_WITH_IP (0x08); * Windows domain join uses 0x16, which does not request it.) */ /* NextClosestSiteName: only if NETLOGON_NT_VERSION_WITH_CLOSEST_SITE (0x10). * Single-site deployment -> no next-closest site, encode as empty (NULL). */ if (ntVersion & 0x10) nlName(b, "", nc); /* NextClosestSiteName (none) */ /* Response NtVersion reflects the format we actually sent (5EX), NOT a raw * echo of the request. Samba: NETLOGON_NT_VERSION_1 | NETLOGON_NT_VERSION_5EX * (+ WITH_IP / WITH_CLOSEST_SITE if those were requested). Never set the * non-EX NETLOGON_NT_VERSION_5 (0x02) bit here, or Windows parses the wrong * structure and discards the response (DsGetDcName -> ERROR_NO_SUCH_DOMAIN). */ uint32_t respNtVer = 0x00000001u | 0x00000004u; /* V1 | 5EX */ if (ntVersion & 0x08) respNtVer |= 0x08; /* 5EX_WITH_IP */ if (ntVersion & 0x10) respNtVer |= 0x10; /* WITH_CLOSEST_SITE */ nlU32(b, respNtVer); nlU16(b, 0xffff); /* LmNtToken */ nlU16(b, 0xffff); /* Lm20Token */ return b; Loading Loading @@ -1098,10 +1144,22 @@ std::vector<uint8_t> ActiveDirectory::handleCldap(const uint8_t *data, size_t le for (auto &d : _Domains) { if (dnsDomain.empty() || dsdb::iequals(d.meta.dnsName, dnsDomain)) { dom = &d; break; } } if (!dom) if (!dom) { std::cerr << "[CLDAP] netlogon ping DnsDomain='" << dnsDomain << "' user='" << user << "' -> NO MATCHING DOMAIN" << std::endl; return {}; } std::cerr << "[CLDAP] netlogon ping DnsDomain='" << dnsDomain << "' user='" << user << "' ntVer=0x" << std::hex << ntVer << std::dec << " -> reply for " << dom->meta.dnsName << std::endl; auto blob = buildNetlogonResponse(*dom, ntVer, user); { std::string hex; static const char *hc = "0123456789abcdef"; for (uint8_t by : blob) { hex += hc[by >> 4]; hex += hc[by & 0xf]; } std::cerr << "[CLDAP] netlogon blob (" << blob.size() << "B): " << hex << std::endl; } /* searchResultEntry { objectName "", attributes { { "Netlogon", { blob } } } } */ LdapEntry e; Loading @@ -1113,6 +1171,13 @@ std::vector<uint8_t> ActiveDirectory::handleCldap(const uint8_t *data, size_t le auto entry = buildSearchResultEntry(msgId, e); auto done = buildSearchResultDone(msgId, LDAP_SUCCESS); entry.insert(entry.end(), done.begin(), done.end()); { std::string hex; static const char *hc = "0123456789abcdef"; for (uint8_t by : entry) { hex += hc[by >> 4]; hex += hc[by & 0xf]; } std::cerr << "[CLDAP] full LDAP response msgId=" << msgId << " (" << entry.size() << "B): " << hex << std::endl; } return entry; } Loading plugin/dns/dns.cpp +73 −16 Changes for plugin/dns/dns.cpp: 73 added lines, 16 removed lines. Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ #include <cstring> #include <ctime> #include <iostream> #include <set> #include <sys/socket.h> #include <netinet/in.h> Loading Loading @@ -153,19 +154,9 @@ void DnsServer::buildZone() { } } std::vector<uint8_t> DnsServer::answerSection(const std::string &qname, uint16_t qtype, int &answerCount, bool &nameExists) { std::vector<uint8_t> out; std::string key = lower(qname); auto range = _Zone.equal_range(key); nameExists = range.first != range.second; for (auto it = range.first; it != range.second; ++it) { const Record &r = it->second; if (qtype != RR_ANY && qtype != r.type && !(qtype == RR_A && r.type == RR_A)) continue; if (qtype != RR_ANY && r.type != qtype) continue; putName(out, qname); void DnsServer::emitRecord(std::vector<uint8_t> &out, const std::string &owner, const Record &r, int &count) { putName(out, owner); put16(out, r.type); put16(out, 1); /* class IN */ put32(out, 600); /* TTL */ Loading @@ -191,7 +182,7 @@ std::vector<uint8_t> DnsServer::answerSection(const std::string &qname, uint16_t break; case RR_SOA: putName(rdata, r.target); /* mname */ putName(rdata, "hostmaster." + key); /* rname */ putName(rdata, "hostmaster." + lower(owner)); /* rname */ put32(rdata, uint32_t(time(nullptr))); /* serial */ put32(rdata, 900); put32(rdata, 600); /* refresh, retry */ put32(rdata, 86400); put32(rdata, 3600); /* expire, minimum */ Loading @@ -199,7 +190,44 @@ std::vector<uint8_t> DnsServer::answerSection(const std::string &qname, uint16_t } put16(out, uint16_t(rdata.size())); out.insert(out.end(), rdata.begin(), rdata.end()); ++answerCount; ++count; } std::vector<uint8_t> DnsServer::answerSection(const std::string &qname, uint16_t qtype, int &answerCount, bool &nameExists) { std::vector<uint8_t> out; std::string key = lower(qname); auto range = _Zone.equal_range(key); nameExists = range.first != range.second; for (auto it = range.first; it != range.second; ++it) { const Record &r = it->second; if (qtype != RR_ANY && qtype != r.type && !(qtype == RR_A && r.type == RR_A)) continue; if (qtype != RR_ANY && r.type != qtype) continue; emitRecord(out, qname, r, answerCount); } return out; } /* Glue: A records for the targets named by the answered SRV/NS records, so a * DC-locator client gets the DC's address without a second round trip. */ std::vector<uint8_t> DnsServer::additionalSection(const std::string &qname, uint16_t qtype, int &addCount) { std::vector<uint8_t> out; if (qtype != RR_SRV && qtype != RR_NS && qtype != RR_ANY) return out; std::string key = lower(qname); auto range = _Zone.equal_range(key); std::set<std::string> targets; for (auto it = range.first; it != range.second; ++it) if (it->second.type == RR_SRV || it->second.type == RR_NS) targets.insert(lower(it->second.target)); for (const auto &t : targets) { auto ar = _Zone.equal_range(t); for (auto it = ar.first; it != ar.second; ++it) if (it->second.type == RR_A) emitRecord(out, t, it->second, addCount); } return out; } Loading Loading @@ -255,7 +283,16 @@ std::vector<uint8_t> DnsServer::handleQuery(const uint8_t *data, size_t len) { if (lq == lower(_DcHost)) authoritative = true; auto qtName = [](uint16_t t) -> const char * { switch (t) { case 1: return "A"; case 2: return "NS"; case 6: return "SOA"; case 12: return "PTR"; case 15: return "MX"; case 16: return "TXT"; case 28: return "AAAA"; case 33: return "SRV"; case 255: return "ANY"; default: return "?"; } }; if (qclass != 1 || !authoritative) { std::cerr << "[DNS] q='" << qname << "' type=" << qtype << "(" << qtName(qtype) << ") -> REFUSED (not authoritative)" << std::endl; header(0, 5 /* REFUSED */, false); resp.insert(resp.end(), data + 12, data + off); /* echo question */ return resp; Loading @@ -264,10 +301,30 @@ std::vector<uint8_t> DnsServer::handleQuery(const uint8_t *data, size_t len) { int ancount = 0; bool nameExists = false; auto ans = answerSection(qname, qtype, ancount, nameExists); int arcount = 0; auto addl = (ancount > 0) ? additionalSection(qname, qtype, arcount) : std::vector<uint8_t>(); uint16_t rcode = (ancount == 0 && !nameExists) ? 3 /* NXDOMAIN */ : 0; header(uint16_t(ancount), rcode, true); std::cerr << "[DNS] q='" << qname << "' type=" << qtype << "(" << qtName(qtype) << ") -> " << (rcode == 3 ? "NXDOMAIN" : ancount == 0 ? "NODATA" : "OK") << " ancount=" << ancount << " arcount=" << arcount << std::endl; /* header with arcount */ put16(resp, id); { uint16_t f = 0x8000; f |= (opcode & 0xf) << 11; f |= 0x0400; /* AA */ f |= 0x0080; /* RA */ f |= (rcode & 0xf); put16(resp, f); } put16(resp, qd); put16(resp, uint16_t(ancount)); put16(resp, 0); /* nscount */ put16(resp, uint16_t(arcount)); /* arcount */ resp.insert(resp.end(), data + 12, data + off); /* question */ resp.insert(resp.end(), ans.begin(), ans.end()); resp.insert(resp.end(), addl.begin(), addl.end()); return resp; } Loading plugin/dns/dns.h +4 −0 Changes for plugin/dns/dns.h: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -71,6 +71,10 @@ namespace fenrirds { std::vector<uint8_t> handleQuery(const uint8_t *data, size_t len); std::vector<uint8_t> answerSection(const std::string &qname, uint16_t qtype, int &answerCount, bool &nameExists); std::vector<uint8_t> additionalSection(const std::string &qname, uint16_t qtype, int &addCount); void emitRecord(std::vector<uint8_t> &out, const std::string &owner, const Record &r, int &count); AuthBackend &_AdminBackend; std::string _BindAddr; Loading plugin/krb5/krb5.cpp +12 −0 Changes for plugin/krb5/krb5.cpp: 12 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -2871,6 +2871,13 @@ std::vector<uint8_t> Krb5KDC::handleStandardAsReq(const uint8_t *data, size_t le req.cname.realm, "krbtgt"); } { std::string cn; for (auto &p : req.cname.name) { if (!cn.empty()) cn += "/"; cn += p; } std::string sn; for (auto &p : req.sname.name) { if (!sn.empty()) sn += "/"; sn += p; } std::cerr << "[KDC] AS-REQ cname='" << cn << "@" << req.cname.realm << "' sname='" << sn << "' npadata=" << req.padata.size() << std::endl; } /* Resolve the realm from the request */ RealmInfo *ri = resolveRealm(req.cname.realm); if (!ri || !ri->backend) { Loading Loading @@ -3147,6 +3154,11 @@ std::vector<uint8_t> Krb5KDC::handleStandardTgsReq(const uint8_t *data, size_t l req.sname.realm, "krbtgt"); } { std::string sn; for (auto &p : req.sname.name) { if (!sn.empty()) sn += "/"; sn += p; } std::cerr << "[KDC] TGS-REQ sname='" << sn << "@" << req.sname.realm << "'" << std::endl; } /* * Extract the TGT from PA-TGS-REQ (PA-DATA type 1, which contains an AP-REQ). * The AP-REQ contains the TGT ticket + encrypted authenticator. Loading plugin/smb/smb.cpp +61 −5 Changes for plugin/smb/smb.cpp: 61 added lines, 5 removed lines. Original line number Diff line number Diff line Loading @@ -74,16 +74,23 @@ void RpcPipe::handlePdu(const std::vector<uint8_t> &pdu) { std::vector<uint8_t> RpcPipe::dispatch(const rpc::RequestPdu &req) { rpc::DispatchResult res; if (rpc::isLsarpc(_boundAbstract.uuid)) const char *iface = "?"; if (rpc::isLsarpc(_boundAbstract.uuid)) { iface = "lsarpc"; res = rpc::lsarpcDispatch(req.opnum, req.stub, _sessionKey); else if (rpc::isSamr(_boundAbstract.uuid)) } else if (rpc::isSamr(_boundAbstract.uuid)) { iface = "samr"; res = rpc::samrDispatch(req.opnum, req.stub, _sessionKey); else if (rpc::isNetlogon(_boundAbstract.uuid)) } else if (rpc::isNetlogon(_boundAbstract.uuid)) { iface = "netlogon"; res = rpc::netlogonDispatch(req.opnum, req.stub, _sessionKey); else { } else { res.fault = true; res.faultCode = 0x1C010003; /* nca_unk_if */ } std::cerr << "[RPC] " << iface << " opnum=" << req.opnum << " stub=" << req.stub.size() << "B -> " << (res.fault ? "FAULT 0x" : "OK status=") << std::hex << (res.fault ? res.faultCode : res.status) << std::dec << std::endl; if (res.fault) return rpc::buildFault(req.callId, req.contextId, res.faultCode); return rpc::buildResponse(req.callId, req.contextId, res.stub); Loading Loading @@ -134,6 +141,7 @@ void SmbServer::CreateConnection(std::shared_ptr<netplus::con> &res) { res = std::make_shared<netplus::con>(this); } void SmbServer::ConnectEvent(netplus::con &c, const int, ULONG_PTR) { std::cerr << "[SMB] TCP connect" << std::endl; std::lock_guard<std::mutex> l(_Lock); _Conns[&c] = Conn(); } void SmbServer::DisconnectEvent(netplus::con &c, const int, ULONG_PTR) { Loading @@ -148,6 +156,7 @@ void SmbServer::RequestEvent(netplus::con &curcon, const int, ULONG_PTR) { { std::lock_guard<std::mutex> l(_Lock); auto it = _Conns.find(&curcon); if (it != _Conns.end()) c = &it->second; } if (!c) return; std::cerr << "[SMB] recv " << curcon.RecvData.size() << "B" << std::endl; std::vector<uint8_t> rb(reinterpret_cast<const uint8_t*>(curcon.RecvData.data()), reinterpret_cast<const uint8_t*>(curcon.RecvData.data()) + curcon.RecvData.size()); size_t consumed = 0; Loading Loading @@ -176,6 +185,28 @@ std::vector<uint8_t> SmbServer::errorResponse(const smb::Smb2Header &req, uint32 std::vector<uint8_t> SmbServer::handleMessage(Conn &c, const std::vector<uint8_t> &msg, bool &close) { namespace gss = fenrirds::crypto::gss; /* Windows opens a port-445 session with a legacy SMB1 multi-protocol * negotiate (0xFF 'SMB', SMB_COM_NEGOTIATE 0x72) advertising "SMB 2.???". * Reply with an SMB2 NEGOTIATE response carrying the wildcard dialect * 0x02FF; the client then restarts with a real SMB2 NEGOTIATE * (MS-SMB2 3.3.5.3.1). Without this the connection stalls and Windows * reports ERROR_NETNAME_DELETED. */ if (msg.size() >= 5 && msg[0] == 0xFF && msg[1] == 'S' && msg[2] == 'M' && msg[3] == 'B' && msg[4] == 0x72) { std::cerr << "[SMB] SMB1 multi-protocol negotiate -> SMB2 wildcard 0x02FF" << std::endl; smb::Smb2Header h; h.command = smb::SMB2_NEGOTIATE; h.messageId = 0; h.status = smb::STATUS_SUCCESS; h.creditReqResp = 1; auto resp = smb::buildHeader(h); auto spnego = smb::spnegoMechList(); auto neg = smb::buildNegotiateResponse(0x02FF, _ServerGuid, spnego); resp.insert(resp.end(), neg.begin(), neg.end()); return resp; } smb::Smb2Header req; if (!smb::parseHeader(msg.data(), msg.size(), req)) return {}; Loading @@ -200,11 +231,25 @@ std::vector<uint8_t> SmbServer::handleMessage(Conn &c, const std::vector<uint8_t return errorResponse(req, smb::STATUS_ACCESS_DENIED); } { static const char *cn[] = {"NEGOTIATE","SESSION_SETUP","LOGOFF","TREE_CONNECT", "TREE_DISCONNECT","CREATE","CLOSE","FLUSH","READ","WRITE","LOCK","IOCTL", "CANCEL","ECHO","QUERY_DIRECTORY","CHANGE_NOTIFY","QUERY_INFO","SET_INFO","OPLOCK_BREAK"}; const char *nm = (req.command < 19) ? cn[req.command] : "?"; std::cerr << "[SMB] cmd=" << req.command << "(" << nm << ") authed=" << c.authenticated << " mid=" << req.messageId << std::endl; } switch (req.command) { case smb::SMB2_NEGOTIATE: { smb::NegotiateRequest nr; if (!smb::parseNegotiate(body, blen, nr)) return errorResponse(req, smb::STATUS_INVALID_PARAMETER); { std::string hex; static const char *hc="0123456789abcdef"; for (uint8_t by : msg) { hex+=hc[by>>4]; hex+=hc[by&0xf]; } std::cerr << "[SMB] NEGOTIATE req (" << msg.size() << "B): " << hex << std::endl; } bool o311=false,o30=false,o302=false,o21=false; for (uint16_t d : nr.dialects) { if (d==smb::SMB_3_1_1) o311=true; else if (d==smb::SMB_3_0) o30=true; Loading Loading @@ -236,6 +281,12 @@ std::vector<uint8_t> SmbServer::handleMessage(Conn &c, const std::vector<uint8_t auto neg = smb::buildNegotiateResponse(c.dialect, _ServerGuid, spnego); resp.insert(resp.end(), neg.begin(), neg.end()); } { std::string hex; static const char *hc="0123456789abcdef"; for (uint8_t by : resp) { hex+=hc[by>>4]; hex+=hc[by&0xf]; } std::cerr << "[SMB] NEGOTIATE resp dialect=0x" << std::hex << c.dialect << std::dec << " (" << resp.size() << "B): " << hex << std::endl; } return resp; } case smb::SMB2_SESSION_SETUP: { Loading Loading @@ -263,8 +314,13 @@ std::vector<uint8_t> SmbServer::handleMessage(Conn &c, const std::vector<uint8_t std::vector<uint8_t> inner = apReq; { uint16_t id=0; std::vector<uint8_t> x; if (gss::parseInitialToken(apReq, id, x)) inner.swap(x); } GssAcceptResult res; if (!g_KdcPlugin->gssAccept(inner, res)) if (!g_KdcPlugin->gssAccept(inner, res)) { std::cerr << "[SMB] SESSION_SETUP gssAccept FAILED (apReq=" << inner.size() << "B spnego=" << spnego << ") -> LOGON_FAILURE" << std::endl; return errorResponse(req, smb::STATUS_LOGON_FAILURE); } std::cerr << "[SMB] SESSION_SETUP gssAccept OK client='" << res.clientPrincipal << "'" << std::endl; c.authenticated = true; c.clientPrincipal = res.clientPrincipal; Loading Loading
plugin/ad/ad.cpp +89 −24 Changes for plugin/ad/ad.cpp: 89 added lines, 24 removed lines. Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ #include <iostream> #include <cstring> #include <map> #include <algorithm> #include <sstream> #include <memory> Loading Loading @@ -474,6 +475,7 @@ void ActiveDirectory::CreateConnection(std::shared_ptr<netplus::con> &res) { } void ActiveDirectory::ConnectEvent(netplus::con &curcon, const int, ULONG_PTR) { std::cerr << "[LDAP] TCP connect" << std::endl; std::lock_guard<std::mutex> lock(_Lock); _Sessions[&curcon] = LdapSession(); } Loading @@ -488,6 +490,9 @@ void ActiveDirectory::ResponseEvent(netplus::con &, const int, ULONG_PTR) {} void ActiveDirectory::RequestEvent(netplus::con &curcon, const int, ULONG_PTR) { if (curcon.RecvData.empty()) return; std::cerr << "[LDAP] recv " << curcon.RecvData.size() << "B (first=0x" << std::hex << (curcon.RecvData.empty() ? 0 : (unsigned char)curcon.RecvData[0]) << std::dec << ")" << std::endl; LdapSession *session = nullptr; { Loading Loading @@ -656,8 +661,11 @@ std::vector<uint8_t> ActiveDirectory::handleBindRequest(const uint8_t *content, session.bindDN.clear(); return buildLdapMessage(msgId, appResp, buildLdapResult(LDAP_SUCCESS, "", "")); } if (authenticateSimple(bindDN, password, session)) if (authenticateSimple(bindDN, password, session)) { std::cerr << "[LDAP] bind SIMPLE dn='" << bindDN << "' -> SUCCESS" << std::endl; return buildLdapMessage(msgId, appResp, buildLdapResult(LDAP_SUCCESS, "", "")); } std::cerr << "[LDAP] bind SIMPLE dn='" << bindDN << "' -> INVALID_CREDENTIALS" << std::endl; return buildLdapMessage(msgId, appResp, buildLdapResult(LDAP_INVALID_CREDENTIALS, "", "80090308: LdapErr: DSID-0C090447, data 52e")); Loading Loading @@ -693,8 +701,11 @@ std::vector<uint8_t> ActiveDirectory::handleBindRequest(const uint8_t *content, namespace gss = fenrirds::crypto::gss; std::string mech = toLower(mechanism); bool spnego = (mech == "gss-spnego"); std::cerr << "[LDAP] bind SASL mech='" << mechanism << "' gssState=" << int(session.gssState) << " inTok=" << inTok.size() << "B" << std::endl; if (mech != "gssapi" && !spnego) { session.gssState = LdapSession::GssNone; std::cerr << "[LDAP] bind SASL -> AUTH_METHOD_NOT_SUPPORTED (mech)" << std::endl; return bindResp(LDAP_AUTH_METHOD_NOT_SUPPORTED, nullptr); } if (!g_KdcPlugin) Loading @@ -716,8 +727,13 @@ std::vector<uint8_t> ActiveDirectory::handleBindRequest(const uint8_t *content, return bindResp(LDAP_INVALID_CREDENTIALS, nullptr); } GssAcceptResult res; if (!g_KdcPlugin->gssAccept(apReq, res)) if (!g_KdcPlugin->gssAccept(apReq, res)) { std::cerr << "[LDAP] bind SASL gssAccept FAILED (apReq=" << apReq.size() << "B) -> INVALID_CREDENTIALS" << std::endl; return bindResp(LDAP_INVALID_CREDENTIALS, nullptr); } std::cerr << "[LDAP] bind SASL gssAccept OK client='" << res.clientPrincipal << "' etype=" << res.sessionEtype << std::endl; session.gssKey = res.sessionKey; session.gssEtype = res.sessionEtype; session.gssClient = res.clientPrincipal; Loading Loading @@ -761,6 +777,8 @@ std::vector<uint8_t> ActiveDirectory::handleBindRequest(const uint8_t *content, session.authenticated = true; session.bindDN = session.gssClient; session.gssState = LdapSession::GssNone; std::cerr << "[LDAP] bind SASL COMPLETE client='" << session.gssClient << "' ssf=" << session.ssf << " -> SUCCESS" << std::endl; return bindResp(LDAP_SUCCESS, nullptr); } Loading Loading @@ -992,18 +1010,31 @@ LdapEntry ActiveDirectory::renderEntry(const dsdb::Entry &ent, * NETLOGON_SAM_LOGON_RESPONSE_EX) so clients can pick this DC. *****************************************************************************/ namespace { void nlName(std::vector<uint8_t> &o, const std::string &name) { size_t i = 0; while (i < name.size()) { size_t dot = name.find('.', i); if (dot == std::string::npos) dot = name.size(); size_t l = dot - i; if (l == 0) break; o.push_back(uint8_t(l)); for (size_t k = i; k < dot; ++k) o.push_back(uint8_t(name[k])); i = dot + 1; } o.push_back(0); /* RFC1035 DNS name with compression, as required by MS-ADTS 6.3.5 for the * netlogon response. Pointers are relative to the start of the blob (= o[0]). * A shared comp map lets DnsDomainName point at DnsForestName, DnsHostName * reuse the "example.com" suffix, repeated site names collapse, etc. */ void nlName(std::vector<uint8_t> &o, const std::string &name, std::map<std::string, uint16_t> &comp) { std::string rem = name; while (!rem.empty()) { auto it = comp.find(rem); if (it != comp.end()) { uint16_t ptr = 0xC000 | it->second; o.push_back(uint8_t(ptr >> 8)); o.push_back(uint8_t(ptr & 0xff)); return; } if (o.size() < 0x4000) comp[rem] = uint16_t(o.size()); size_t dot = rem.find('.'); std::string label = (dot == std::string::npos) ? rem : rem.substr(0, dot); if (label.empty()) break; o.push_back(uint8_t(label.size())); for (char c : label) o.push_back(uint8_t(c)); rem = (dot == std::string::npos) ? std::string() : rem.substr(dot + 1); } o.push_back(0); /* root label terminates an uncompressed (sub)name */ } void nlU16(std::vector<uint8_t> &o, uint16_t v) { o.push_back(v & 0xff); o.push_back(v >> 8); } void nlU32(std::vector<uint8_t> &o, uint32_t v) { for (int i = 0; i < 4; ++i) o.push_back((v >> (8 * i)) & 0xff); } Loading @@ -1019,6 +1050,7 @@ std::vector<uint8_t> ActiveDirectory::buildNetlogonResponse(const Dom &d, uint32 uint32_t flags = 0x1 | 0x4 | 0x8 | 0x10 | 0x20 | 0x40 | 0x80 | 0x100 | 0x200 | 0x20000000u | 0x40000000u | 0x80000000u; /* PDC/GC/LDAP/DS/KDC/TIMESERV/CLOSEST/WRITABLE/GOODTIME + DNS_{CONTROLLER,DOMAIN,FOREST} */ std::map<std::string, uint16_t> nc; /* DNS name compression context (per blob) */ nlU16(b, 23); /* Opcode = LOGON_SAM_LOGON_RESPONSE_EX */ nlU16(b, 0); /* Sbz */ nlU32(b, flags); Loading @@ -1027,15 +1059,29 @@ std::vector<uint8_t> ActiveDirectory::buildNetlogonResponse(const Dom &d, uint32 if (auto head = d.dir->byDn(d.meta.baseDn)) std::memcpy(guid, head->objectGuid.value, 16); b.insert(b.end(), guid, guid + 16); nlName(b, d.meta.dnsName); /* DnsForestName (= domain, single-domain forest) */ nlName(b, d.meta.dnsName); /* DnsDomainName */ nlName(b, host); /* DnsHostName */ nlName(b, d.meta.netbios); /* NetbiosDomainName */ nlName(b, dsdb::upper(comp)); /* NetbiosComputerName */ nlName(b, user); /* UserName (echo / empty) */ nlName(b, site); /* DcSiteName */ nlName(b, site); /* ClientSiteName */ nlU32(b, ntVersion ? ntVersion : 0x00000005); nlName(b, d.meta.dnsName, nc); /* DnsForestName (= domain, single-domain forest) */ nlName(b, d.meta.dnsName, nc); /* DnsDomainName */ nlName(b, host, nc); /* DnsHostName */ nlName(b, d.meta.netbios, nc); /* NetbiosDomainName */ nlName(b, dsdb::upper(comp), nc); /* NetbiosComputerName */ nlName(b, user, nc); /* UserName (echo / empty) */ nlName(b, site, nc); /* DcSiteName */ nlName(b, site, nc); /* ClientSiteName */ /* (DcSockAddr would go here iff NETLOGON_NT_VERSION_5EX_WITH_IP (0x08); * Windows domain join uses 0x16, which does not request it.) */ /* NextClosestSiteName: only if NETLOGON_NT_VERSION_WITH_CLOSEST_SITE (0x10). * Single-site deployment -> no next-closest site, encode as empty (NULL). */ if (ntVersion & 0x10) nlName(b, "", nc); /* NextClosestSiteName (none) */ /* Response NtVersion reflects the format we actually sent (5EX), NOT a raw * echo of the request. Samba: NETLOGON_NT_VERSION_1 | NETLOGON_NT_VERSION_5EX * (+ WITH_IP / WITH_CLOSEST_SITE if those were requested). Never set the * non-EX NETLOGON_NT_VERSION_5 (0x02) bit here, or Windows parses the wrong * structure and discards the response (DsGetDcName -> ERROR_NO_SUCH_DOMAIN). */ uint32_t respNtVer = 0x00000001u | 0x00000004u; /* V1 | 5EX */ if (ntVersion & 0x08) respNtVer |= 0x08; /* 5EX_WITH_IP */ if (ntVersion & 0x10) respNtVer |= 0x10; /* WITH_CLOSEST_SITE */ nlU32(b, respNtVer); nlU16(b, 0xffff); /* LmNtToken */ nlU16(b, 0xffff); /* Lm20Token */ return b; Loading Loading @@ -1098,10 +1144,22 @@ std::vector<uint8_t> ActiveDirectory::handleCldap(const uint8_t *data, size_t le for (auto &d : _Domains) { if (dnsDomain.empty() || dsdb::iequals(d.meta.dnsName, dnsDomain)) { dom = &d; break; } } if (!dom) if (!dom) { std::cerr << "[CLDAP] netlogon ping DnsDomain='" << dnsDomain << "' user='" << user << "' -> NO MATCHING DOMAIN" << std::endl; return {}; } std::cerr << "[CLDAP] netlogon ping DnsDomain='" << dnsDomain << "' user='" << user << "' ntVer=0x" << std::hex << ntVer << std::dec << " -> reply for " << dom->meta.dnsName << std::endl; auto blob = buildNetlogonResponse(*dom, ntVer, user); { std::string hex; static const char *hc = "0123456789abcdef"; for (uint8_t by : blob) { hex += hc[by >> 4]; hex += hc[by & 0xf]; } std::cerr << "[CLDAP] netlogon blob (" << blob.size() << "B): " << hex << std::endl; } /* searchResultEntry { objectName "", attributes { { "Netlogon", { blob } } } } */ LdapEntry e; Loading @@ -1113,6 +1171,13 @@ std::vector<uint8_t> ActiveDirectory::handleCldap(const uint8_t *data, size_t le auto entry = buildSearchResultEntry(msgId, e); auto done = buildSearchResultDone(msgId, LDAP_SUCCESS); entry.insert(entry.end(), done.begin(), done.end()); { std::string hex; static const char *hc = "0123456789abcdef"; for (uint8_t by : entry) { hex += hc[by >> 4]; hex += hc[by & 0xf]; } std::cerr << "[CLDAP] full LDAP response msgId=" << msgId << " (" << entry.size() << "B): " << hex << std::endl; } return entry; } Loading
plugin/dns/dns.cpp +73 −16 Changes for plugin/dns/dns.cpp: 73 added lines, 16 removed lines. Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ #include <cstring> #include <ctime> #include <iostream> #include <set> #include <sys/socket.h> #include <netinet/in.h> Loading Loading @@ -153,19 +154,9 @@ void DnsServer::buildZone() { } } std::vector<uint8_t> DnsServer::answerSection(const std::string &qname, uint16_t qtype, int &answerCount, bool &nameExists) { std::vector<uint8_t> out; std::string key = lower(qname); auto range = _Zone.equal_range(key); nameExists = range.first != range.second; for (auto it = range.first; it != range.second; ++it) { const Record &r = it->second; if (qtype != RR_ANY && qtype != r.type && !(qtype == RR_A && r.type == RR_A)) continue; if (qtype != RR_ANY && r.type != qtype) continue; putName(out, qname); void DnsServer::emitRecord(std::vector<uint8_t> &out, const std::string &owner, const Record &r, int &count) { putName(out, owner); put16(out, r.type); put16(out, 1); /* class IN */ put32(out, 600); /* TTL */ Loading @@ -191,7 +182,7 @@ std::vector<uint8_t> DnsServer::answerSection(const std::string &qname, uint16_t break; case RR_SOA: putName(rdata, r.target); /* mname */ putName(rdata, "hostmaster." + key); /* rname */ putName(rdata, "hostmaster." + lower(owner)); /* rname */ put32(rdata, uint32_t(time(nullptr))); /* serial */ put32(rdata, 900); put32(rdata, 600); /* refresh, retry */ put32(rdata, 86400); put32(rdata, 3600); /* expire, minimum */ Loading @@ -199,7 +190,44 @@ std::vector<uint8_t> DnsServer::answerSection(const std::string &qname, uint16_t } put16(out, uint16_t(rdata.size())); out.insert(out.end(), rdata.begin(), rdata.end()); ++answerCount; ++count; } std::vector<uint8_t> DnsServer::answerSection(const std::string &qname, uint16_t qtype, int &answerCount, bool &nameExists) { std::vector<uint8_t> out; std::string key = lower(qname); auto range = _Zone.equal_range(key); nameExists = range.first != range.second; for (auto it = range.first; it != range.second; ++it) { const Record &r = it->second; if (qtype != RR_ANY && qtype != r.type && !(qtype == RR_A && r.type == RR_A)) continue; if (qtype != RR_ANY && r.type != qtype) continue; emitRecord(out, qname, r, answerCount); } return out; } /* Glue: A records for the targets named by the answered SRV/NS records, so a * DC-locator client gets the DC's address without a second round trip. */ std::vector<uint8_t> DnsServer::additionalSection(const std::string &qname, uint16_t qtype, int &addCount) { std::vector<uint8_t> out; if (qtype != RR_SRV && qtype != RR_NS && qtype != RR_ANY) return out; std::string key = lower(qname); auto range = _Zone.equal_range(key); std::set<std::string> targets; for (auto it = range.first; it != range.second; ++it) if (it->second.type == RR_SRV || it->second.type == RR_NS) targets.insert(lower(it->second.target)); for (const auto &t : targets) { auto ar = _Zone.equal_range(t); for (auto it = ar.first; it != ar.second; ++it) if (it->second.type == RR_A) emitRecord(out, t, it->second, addCount); } return out; } Loading Loading @@ -255,7 +283,16 @@ std::vector<uint8_t> DnsServer::handleQuery(const uint8_t *data, size_t len) { if (lq == lower(_DcHost)) authoritative = true; auto qtName = [](uint16_t t) -> const char * { switch (t) { case 1: return "A"; case 2: return "NS"; case 6: return "SOA"; case 12: return "PTR"; case 15: return "MX"; case 16: return "TXT"; case 28: return "AAAA"; case 33: return "SRV"; case 255: return "ANY"; default: return "?"; } }; if (qclass != 1 || !authoritative) { std::cerr << "[DNS] q='" << qname << "' type=" << qtype << "(" << qtName(qtype) << ") -> REFUSED (not authoritative)" << std::endl; header(0, 5 /* REFUSED */, false); resp.insert(resp.end(), data + 12, data + off); /* echo question */ return resp; Loading @@ -264,10 +301,30 @@ std::vector<uint8_t> DnsServer::handleQuery(const uint8_t *data, size_t len) { int ancount = 0; bool nameExists = false; auto ans = answerSection(qname, qtype, ancount, nameExists); int arcount = 0; auto addl = (ancount > 0) ? additionalSection(qname, qtype, arcount) : std::vector<uint8_t>(); uint16_t rcode = (ancount == 0 && !nameExists) ? 3 /* NXDOMAIN */ : 0; header(uint16_t(ancount), rcode, true); std::cerr << "[DNS] q='" << qname << "' type=" << qtype << "(" << qtName(qtype) << ") -> " << (rcode == 3 ? "NXDOMAIN" : ancount == 0 ? "NODATA" : "OK") << " ancount=" << ancount << " arcount=" << arcount << std::endl; /* header with arcount */ put16(resp, id); { uint16_t f = 0x8000; f |= (opcode & 0xf) << 11; f |= 0x0400; /* AA */ f |= 0x0080; /* RA */ f |= (rcode & 0xf); put16(resp, f); } put16(resp, qd); put16(resp, uint16_t(ancount)); put16(resp, 0); /* nscount */ put16(resp, uint16_t(arcount)); /* arcount */ resp.insert(resp.end(), data + 12, data + off); /* question */ resp.insert(resp.end(), ans.begin(), ans.end()); resp.insert(resp.end(), addl.begin(), addl.end()); return resp; } Loading
plugin/dns/dns.h +4 −0 Changes for plugin/dns/dns.h: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -71,6 +71,10 @@ namespace fenrirds { std::vector<uint8_t> handleQuery(const uint8_t *data, size_t len); std::vector<uint8_t> answerSection(const std::string &qname, uint16_t qtype, int &answerCount, bool &nameExists); std::vector<uint8_t> additionalSection(const std::string &qname, uint16_t qtype, int &addCount); void emitRecord(std::vector<uint8_t> &out, const std::string &owner, const Record &r, int &count); AuthBackend &_AdminBackend; std::string _BindAddr; Loading
plugin/krb5/krb5.cpp +12 −0 Changes for plugin/krb5/krb5.cpp: 12 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -2871,6 +2871,13 @@ std::vector<uint8_t> Krb5KDC::handleStandardAsReq(const uint8_t *data, size_t le req.cname.realm, "krbtgt"); } { std::string cn; for (auto &p : req.cname.name) { if (!cn.empty()) cn += "/"; cn += p; } std::string sn; for (auto &p : req.sname.name) { if (!sn.empty()) sn += "/"; sn += p; } std::cerr << "[KDC] AS-REQ cname='" << cn << "@" << req.cname.realm << "' sname='" << sn << "' npadata=" << req.padata.size() << std::endl; } /* Resolve the realm from the request */ RealmInfo *ri = resolveRealm(req.cname.realm); if (!ri || !ri->backend) { Loading Loading @@ -3147,6 +3154,11 @@ std::vector<uint8_t> Krb5KDC::handleStandardTgsReq(const uint8_t *data, size_t l req.sname.realm, "krbtgt"); } { std::string sn; for (auto &p : req.sname.name) { if (!sn.empty()) sn += "/"; sn += p; } std::cerr << "[KDC] TGS-REQ sname='" << sn << "@" << req.sname.realm << "'" << std::endl; } /* * Extract the TGT from PA-TGS-REQ (PA-DATA type 1, which contains an AP-REQ). * The AP-REQ contains the TGT ticket + encrypted authenticator. Loading
plugin/smb/smb.cpp +61 −5 Changes for plugin/smb/smb.cpp: 61 added lines, 5 removed lines. Original line number Diff line number Diff line Loading @@ -74,16 +74,23 @@ void RpcPipe::handlePdu(const std::vector<uint8_t> &pdu) { std::vector<uint8_t> RpcPipe::dispatch(const rpc::RequestPdu &req) { rpc::DispatchResult res; if (rpc::isLsarpc(_boundAbstract.uuid)) const char *iface = "?"; if (rpc::isLsarpc(_boundAbstract.uuid)) { iface = "lsarpc"; res = rpc::lsarpcDispatch(req.opnum, req.stub, _sessionKey); else if (rpc::isSamr(_boundAbstract.uuid)) } else if (rpc::isSamr(_boundAbstract.uuid)) { iface = "samr"; res = rpc::samrDispatch(req.opnum, req.stub, _sessionKey); else if (rpc::isNetlogon(_boundAbstract.uuid)) } else if (rpc::isNetlogon(_boundAbstract.uuid)) { iface = "netlogon"; res = rpc::netlogonDispatch(req.opnum, req.stub, _sessionKey); else { } else { res.fault = true; res.faultCode = 0x1C010003; /* nca_unk_if */ } std::cerr << "[RPC] " << iface << " opnum=" << req.opnum << " stub=" << req.stub.size() << "B -> " << (res.fault ? "FAULT 0x" : "OK status=") << std::hex << (res.fault ? res.faultCode : res.status) << std::dec << std::endl; if (res.fault) return rpc::buildFault(req.callId, req.contextId, res.faultCode); return rpc::buildResponse(req.callId, req.contextId, res.stub); Loading Loading @@ -134,6 +141,7 @@ void SmbServer::CreateConnection(std::shared_ptr<netplus::con> &res) { res = std::make_shared<netplus::con>(this); } void SmbServer::ConnectEvent(netplus::con &c, const int, ULONG_PTR) { std::cerr << "[SMB] TCP connect" << std::endl; std::lock_guard<std::mutex> l(_Lock); _Conns[&c] = Conn(); } void SmbServer::DisconnectEvent(netplus::con &c, const int, ULONG_PTR) { Loading @@ -148,6 +156,7 @@ void SmbServer::RequestEvent(netplus::con &curcon, const int, ULONG_PTR) { { std::lock_guard<std::mutex> l(_Lock); auto it = _Conns.find(&curcon); if (it != _Conns.end()) c = &it->second; } if (!c) return; std::cerr << "[SMB] recv " << curcon.RecvData.size() << "B" << std::endl; std::vector<uint8_t> rb(reinterpret_cast<const uint8_t*>(curcon.RecvData.data()), reinterpret_cast<const uint8_t*>(curcon.RecvData.data()) + curcon.RecvData.size()); size_t consumed = 0; Loading Loading @@ -176,6 +185,28 @@ std::vector<uint8_t> SmbServer::errorResponse(const smb::Smb2Header &req, uint32 std::vector<uint8_t> SmbServer::handleMessage(Conn &c, const std::vector<uint8_t> &msg, bool &close) { namespace gss = fenrirds::crypto::gss; /* Windows opens a port-445 session with a legacy SMB1 multi-protocol * negotiate (0xFF 'SMB', SMB_COM_NEGOTIATE 0x72) advertising "SMB 2.???". * Reply with an SMB2 NEGOTIATE response carrying the wildcard dialect * 0x02FF; the client then restarts with a real SMB2 NEGOTIATE * (MS-SMB2 3.3.5.3.1). Without this the connection stalls and Windows * reports ERROR_NETNAME_DELETED. */ if (msg.size() >= 5 && msg[0] == 0xFF && msg[1] == 'S' && msg[2] == 'M' && msg[3] == 'B' && msg[4] == 0x72) { std::cerr << "[SMB] SMB1 multi-protocol negotiate -> SMB2 wildcard 0x02FF" << std::endl; smb::Smb2Header h; h.command = smb::SMB2_NEGOTIATE; h.messageId = 0; h.status = smb::STATUS_SUCCESS; h.creditReqResp = 1; auto resp = smb::buildHeader(h); auto spnego = smb::spnegoMechList(); auto neg = smb::buildNegotiateResponse(0x02FF, _ServerGuid, spnego); resp.insert(resp.end(), neg.begin(), neg.end()); return resp; } smb::Smb2Header req; if (!smb::parseHeader(msg.data(), msg.size(), req)) return {}; Loading @@ -200,11 +231,25 @@ std::vector<uint8_t> SmbServer::handleMessage(Conn &c, const std::vector<uint8_t return errorResponse(req, smb::STATUS_ACCESS_DENIED); } { static const char *cn[] = {"NEGOTIATE","SESSION_SETUP","LOGOFF","TREE_CONNECT", "TREE_DISCONNECT","CREATE","CLOSE","FLUSH","READ","WRITE","LOCK","IOCTL", "CANCEL","ECHO","QUERY_DIRECTORY","CHANGE_NOTIFY","QUERY_INFO","SET_INFO","OPLOCK_BREAK"}; const char *nm = (req.command < 19) ? cn[req.command] : "?"; std::cerr << "[SMB] cmd=" << req.command << "(" << nm << ") authed=" << c.authenticated << " mid=" << req.messageId << std::endl; } switch (req.command) { case smb::SMB2_NEGOTIATE: { smb::NegotiateRequest nr; if (!smb::parseNegotiate(body, blen, nr)) return errorResponse(req, smb::STATUS_INVALID_PARAMETER); { std::string hex; static const char *hc="0123456789abcdef"; for (uint8_t by : msg) { hex+=hc[by>>4]; hex+=hc[by&0xf]; } std::cerr << "[SMB] NEGOTIATE req (" << msg.size() << "B): " << hex << std::endl; } bool o311=false,o30=false,o302=false,o21=false; for (uint16_t d : nr.dialects) { if (d==smb::SMB_3_1_1) o311=true; else if (d==smb::SMB_3_0) o30=true; Loading Loading @@ -236,6 +281,12 @@ std::vector<uint8_t> SmbServer::handleMessage(Conn &c, const std::vector<uint8_t auto neg = smb::buildNegotiateResponse(c.dialect, _ServerGuid, spnego); resp.insert(resp.end(), neg.begin(), neg.end()); } { std::string hex; static const char *hc="0123456789abcdef"; for (uint8_t by : resp) { hex+=hc[by>>4]; hex+=hc[by&0xf]; } std::cerr << "[SMB] NEGOTIATE resp dialect=0x" << std::hex << c.dialect << std::dec << " (" << resp.size() << "B): " << hex << std::endl; } return resp; } case smb::SMB2_SESSION_SETUP: { Loading Loading @@ -263,8 +314,13 @@ std::vector<uint8_t> SmbServer::handleMessage(Conn &c, const std::vector<uint8_t std::vector<uint8_t> inner = apReq; { uint16_t id=0; std::vector<uint8_t> x; if (gss::parseInitialToken(apReq, id, x)) inner.swap(x); } GssAcceptResult res; if (!g_KdcPlugin->gssAccept(inner, res)) if (!g_KdcPlugin->gssAccept(inner, res)) { std::cerr << "[SMB] SESSION_SETUP gssAccept FAILED (apReq=" << inner.size() << "B spnego=" << spnego << ") -> LOGON_FAILURE" << std::endl; return errorResponse(req, smb::STATUS_LOGON_FAILURE); } std::cerr << "[SMB] SESSION_SETUP gssAccept OK client='" << res.clientPrincipal << "'" << std::endl; c.authenticated = true; c.clientPrincipal = res.clientPrincipal; Loading