Loading CMakeLists.txt +2 −0 Changes for CMakeLists.txt: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -58,6 +58,8 @@ add_library(adconnect_core STATIC src/sasl.cpp src/discover.cpp src/join.cpp src/acceptor.cpp src/home.cpp ) target_link_libraries(adconnect_core PUBLIC adconnect_proto confplus::confplus netplus::netplus Threads::Threads) Loading src/acceptor.cpp 0 → 100644 +175 −0 Changes for src/acceptor.cpp: 175 added lines, 0 removed lines. Original line number Diff line number Diff line /******************************************************************************* Copyright (c) 2026, Jan Koester jan.koester@gmx.net All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include "acceptor.h" #include <netplus/random.h> #include "ber.h" namespace adconnect { namespace { using krb5::Bytes; namespace der = krb5::der; const Bytes SpnegoOid = Bytes("\x06\x06\x2b\x06\x01\x05\x05\x02", 8); const Bytes KrbOid = Bytes("\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02", 11); /* Microsoft's (an old Windows typo of the above, still sent first) */ const Bytes MsKrbOid = Bytes("\x06\x09\x2a\x86\x48\x82\xf7\x12\x01\x02\x02", 11); constexpr int Skew = 300; enum Usage { UsageTicket = 2, UsageApReqAuthenticator = 11, UsageApRepEncPart = 12 }; [[noreturn]] void refuse(const std::string &why) { throw std::runtime_error("Kerberos login refused: " + why); } /* the OID TLV at the start of s, and its size */ Bytes leadingOid(const Bytes &s) { if (s.size() < 2 || s[0] != 0x06) refuse("no mechanism OID"); size_t n = ber::elementSize(s.data(), s.size()); if (!n || n > s.size()) refuse("bad mechanism OID"); return s.substr(0, n); } } // namespace bool Acceptor::replayed(const std::string &key, time_t now) { std::lock_guard<std::mutex> l(_lock); for (auto it = _seen.begin(); it != _seen.end();) it = it->second < now ? _seen.erase(it) : std::next(it); if (_seen.count(key)) return true; _seen[key] = now + 2 * Skew; return false; } Accepted Acceptor::accept(const Bytes &token) { try { /* SPNEGO (NegTokenInit) around a Kerberos token, or the Kerberos token itself */ bool spnego = false; Bytes mechToken = token; ber::Element outer = ber::Reader(token).next(0x60); Bytes oid = leadingOid(outer.content); if (oid == SpnegoOid) { spnego = true; ber::Element init = ber::Reader(outer.content.substr(oid.size())).next(0xa0); auto f = der::fields(ber::Reader(init.content).next(ber::Sequence).content); if (!f.count(2)) refuse("SPNEGO without a mechanism token (no Kerberos ticket: the client tried NTLM?)"); mechToken = der::octets(f[2]); outer = ber::Reader(mechToken).next(0x60); oid = leadingOid(outer.content); } if (oid != KrbOid && oid != MsKrbOid) refuse("not a Kerberos token"); Bytes inner = outer.content.substr(oid.size()); if (inner.size() < 3 || inner.compare(0, 2, Bytes("\x01\x00", 2)) != 0) refuse("not an AP-REQ"); /* the AP-REQ: ticket and authenticator */ auto ap = der::fields(der::application(inner.substr(2), 14)); if (!ap.count(3) || !ap.count(4)) refuse("incomplete AP-REQ"); auto tk = der::fields(der::application(ap[3], 1)); if (!tk.count(1) || !tk.count(3)) refuse("incomplete ticket"); std::string realm = der::string(tk[1]); krb5::EncryptedData tenc = krb5::parseEncryptedData(tk[3]); krb5::Key serviceKey; if (!_keys(realm, tenc.etype, serviceKey)) refuse("no " + krb5::etypeName(tenc.etype) + " key for realm " + realm); Bytes tplain; try { tplain = krb5::decrypt(serviceKey, UsageTicket, tenc.cipher); } catch (const krb5::CryptoError &) { refuse("the ticket doesn't decrypt with this system's key (joined again since?)"); } auto et = der::fields(der::application(tplain, 3)); if (!et.count(1) || !et.count(2) || !et.count(3) || !et.count(7)) refuse("incomplete ticket contents"); krb5::Key sessionKey = krb5::parseEncryptionKey(et[1]); std::string crealm = der::string(et[2]); auto cn = der::fields(ber::Reader(et[3]).next(ber::Sequence).content); krb5::Principal client; client.realm = crealm; if (cn.count(0)) client.type = (int)der::integer(cn[0]); if (cn.count(1)) { ber::Reader names(ber::Reader(cn[1]).next(ber::Sequence).content); while (!names.atEnd()) client.names.push_back(names.next().content); } time_t now = time(nullptr); time_t endtime = krb5::parseKerberosTime(der::string(et[7])); if (!endtime || now > endtime + Skew) refuse("the ticket expired"); if (et.count(6) && krb5::parseKerberosTime(der::string(et[6])) > now + Skew) refuse("the ticket isn't valid yet"); krb5::EncryptedData aenc = krb5::parseEncryptedData(ap[4]); Bytes aplain; try { aplain = krb5::decrypt(sessionKey, UsageApReqAuthenticator, aenc.cipher); } catch (const krb5::CryptoError &) { refuse("the authenticator doesn't decrypt"); } auto au = der::fields(der::application(aplain, 2)); if (!au.count(1) || !au.count(2) || !au.count(4) || !au.count(5)) refuse("incomplete authenticator"); if (der::string(au[1]) != crealm || au[2] != et[3]) refuse("authenticator and ticket name different clients"); std::string ctimeText = der::string(au[5]); time_t ctime = krb5::parseKerberosTime(ctimeText); long long cusec = der::integer(au[4]); long diff = (long)(now - ctime); if (!ctime || diff > Skew || diff < -Skew) refuse("the clocks differ by " + std::to_string(diff < 0 ? -diff : diff) + " s (more than 5 minutes)"); if (replayed(client.str() + "|" + ctimeText + "|" + std::to_string(cusec), now)) refuse("a replayed authenticator"); Accepted a; a.client = client; a.endtime = endtime; a.sessionKey = au.count(6) ? krb5::parseEncryptionKey(au[6]).value : sessionKey.value; /* the AP-REP: the authenticator's time back, under the ticket's session key */ uint8_t r[4]; netplus::fillRandomBytes(r, 4); uint32_t seq = ((uint32_t)r[0] << 24 | (uint32_t)r[1] << 16 | (uint32_t)r[2] << 8 | r[3]) & 0x3fffffff; Bytes encPart = ber::tlv(ber::applicationConstructed(27), ber::sequence(der::ctx(0, ber::tlv(0x18, ctimeText)) + der::ctx(1, ber::integer(cusec)) + der::ctx(3, ber::integer(seq)))); Bytes apRep = ber::tlv(ber::applicationConstructed(15), ber::sequence(der::ctx(0, ber::integer(5)) + der::ctx(1, ber::integer(15)) + der::ctx(2, der::encryptedData(sessionKey.etype, krb5::encrypt(sessionKey, UsageApRepEncPart, encPart))))); Bytes gss = ber::tlv(0x60, KrbOid + Bytes("\x02\x00", 2) + apRep); if (spnego) { /* NegTokenResp: accept-completed, the client's Kerberos mechanism, our token */ a.response = ber::tlv(0xa1, ber::sequence(der::ctx(0, ber::enumerated(0)) + der::ctx(1, oid) + der::ctx(2, ber::octets(gss)))); } else { a.response = gss; } return a; } catch (const ber::Error &e) { refuse(std::string("malformed token: ") + e.what()); } catch (const krb5::Error &e) { refuse(e.what()); } } } // namespace adconnect src/acceptor.h 0 → 100644 +74 −0 Changes for src/acceptor.h: 74 added lines, 0 removed lines. Original line number Diff line number Diff line /******************************************************************************* Copyright (c) 2026, Jan Koester jan.koester@gmx.net All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ /* The accepting side of Kerberos, for SMB logins (ksmbd asks sharedd, * sharedd asks adconnectd): the client's token -- SPNEGO with a Kerberos * mechanism token, as Windows sends it, or a bare Kerberos GSS token -- * checked against our own account's keys: the ticket decrypted, the * authenticator fresh (5 minutes) and not seen before. The answer is the * client, the session key (the authenticator's subkey) and the token to * send back: the AP-REP, in SPNEGO if the client used SPNEGO. */ #pragma once #include <ctime> #include <functional> #include <map> #include <mutex> #include <stdexcept> #include <string> #include "krb5.h" namespace adconnect { struct Accepted { krb5::Principal client; krb5::Bytes sessionKey; krb5::Bytes response; time_t endtime = 0; /* the ticket's */ }; class Acceptor { public: /* our key of etype for a ticket of realm; false if we have none */ using KeySource = std::function<bool(const std::string &realm, int etype, krb5::Key &key)>; explicit Acceptor(KeySource keys) : _keys(std::move(keys)) {} /* throws std::runtime_error saying why the token isn't accepted */ Accepted accept(const krb5::Bytes &token); private: bool replayed(const std::string &key, time_t now); KeySource _keys; std::mutex _lock; std::map<std::string, time_t> _seen; /* authenticator -> until when it's remembered */ }; } // namespace adconnect src/config.cpp +1 −0 Changes for src/config.cpp: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -257,6 +257,7 @@ void readDomain(const Scope &s, Config &cfg, bool multi, bool &idBaseSet) { readString(s, "home", cfg.home); readString(s, "shell", cfg.shell); readBool(s, "lowercase_names", cfg.lowercase); readBool(s, "create_home", cfg.createHome); cfg.qualified = multi; readBool(s, "qualified_names", cfg.qualified); readBool(s, "enumerate", cfg.enumerate); Loading src/config.h +1 −0 Changes for src/config.h: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -75,6 +75,7 @@ struct Config { std::string home = "/home/%d/%n"; /* %d domain, %n the account name, %u the name (with @domain if qualified) */ std::string shell = "/bin/sh"; bool lowercase = true; bool createHome = true; /* make a missing home directory at a user's first lookup */ bool qualified = false; /* names user@domain (default with several domains) */ bool enumerate = false; std::string userFilter; /* ANDed to every user search */ Loading Loading
CMakeLists.txt +2 −0 Changes for CMakeLists.txt: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -58,6 +58,8 @@ add_library(adconnect_core STATIC src/sasl.cpp src/discover.cpp src/join.cpp src/acceptor.cpp src/home.cpp ) target_link_libraries(adconnect_core PUBLIC adconnect_proto confplus::confplus netplus::netplus Threads::Threads) Loading
src/acceptor.cpp 0 → 100644 +175 −0 Changes for src/acceptor.cpp: 175 added lines, 0 removed lines. Original line number Diff line number Diff line /******************************************************************************* Copyright (c) 2026, Jan Koester jan.koester@gmx.net All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include "acceptor.h" #include <netplus/random.h> #include "ber.h" namespace adconnect { namespace { using krb5::Bytes; namespace der = krb5::der; const Bytes SpnegoOid = Bytes("\x06\x06\x2b\x06\x01\x05\x05\x02", 8); const Bytes KrbOid = Bytes("\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02", 11); /* Microsoft's (an old Windows typo of the above, still sent first) */ const Bytes MsKrbOid = Bytes("\x06\x09\x2a\x86\x48\x82\xf7\x12\x01\x02\x02", 11); constexpr int Skew = 300; enum Usage { UsageTicket = 2, UsageApReqAuthenticator = 11, UsageApRepEncPart = 12 }; [[noreturn]] void refuse(const std::string &why) { throw std::runtime_error("Kerberos login refused: " + why); } /* the OID TLV at the start of s, and its size */ Bytes leadingOid(const Bytes &s) { if (s.size() < 2 || s[0] != 0x06) refuse("no mechanism OID"); size_t n = ber::elementSize(s.data(), s.size()); if (!n || n > s.size()) refuse("bad mechanism OID"); return s.substr(0, n); } } // namespace bool Acceptor::replayed(const std::string &key, time_t now) { std::lock_guard<std::mutex> l(_lock); for (auto it = _seen.begin(); it != _seen.end();) it = it->second < now ? _seen.erase(it) : std::next(it); if (_seen.count(key)) return true; _seen[key] = now + 2 * Skew; return false; } Accepted Acceptor::accept(const Bytes &token) { try { /* SPNEGO (NegTokenInit) around a Kerberos token, or the Kerberos token itself */ bool spnego = false; Bytes mechToken = token; ber::Element outer = ber::Reader(token).next(0x60); Bytes oid = leadingOid(outer.content); if (oid == SpnegoOid) { spnego = true; ber::Element init = ber::Reader(outer.content.substr(oid.size())).next(0xa0); auto f = der::fields(ber::Reader(init.content).next(ber::Sequence).content); if (!f.count(2)) refuse("SPNEGO without a mechanism token (no Kerberos ticket: the client tried NTLM?)"); mechToken = der::octets(f[2]); outer = ber::Reader(mechToken).next(0x60); oid = leadingOid(outer.content); } if (oid != KrbOid && oid != MsKrbOid) refuse("not a Kerberos token"); Bytes inner = outer.content.substr(oid.size()); if (inner.size() < 3 || inner.compare(0, 2, Bytes("\x01\x00", 2)) != 0) refuse("not an AP-REQ"); /* the AP-REQ: ticket and authenticator */ auto ap = der::fields(der::application(inner.substr(2), 14)); if (!ap.count(3) || !ap.count(4)) refuse("incomplete AP-REQ"); auto tk = der::fields(der::application(ap[3], 1)); if (!tk.count(1) || !tk.count(3)) refuse("incomplete ticket"); std::string realm = der::string(tk[1]); krb5::EncryptedData tenc = krb5::parseEncryptedData(tk[3]); krb5::Key serviceKey; if (!_keys(realm, tenc.etype, serviceKey)) refuse("no " + krb5::etypeName(tenc.etype) + " key for realm " + realm); Bytes tplain; try { tplain = krb5::decrypt(serviceKey, UsageTicket, tenc.cipher); } catch (const krb5::CryptoError &) { refuse("the ticket doesn't decrypt with this system's key (joined again since?)"); } auto et = der::fields(der::application(tplain, 3)); if (!et.count(1) || !et.count(2) || !et.count(3) || !et.count(7)) refuse("incomplete ticket contents"); krb5::Key sessionKey = krb5::parseEncryptionKey(et[1]); std::string crealm = der::string(et[2]); auto cn = der::fields(ber::Reader(et[3]).next(ber::Sequence).content); krb5::Principal client; client.realm = crealm; if (cn.count(0)) client.type = (int)der::integer(cn[0]); if (cn.count(1)) { ber::Reader names(ber::Reader(cn[1]).next(ber::Sequence).content); while (!names.atEnd()) client.names.push_back(names.next().content); } time_t now = time(nullptr); time_t endtime = krb5::parseKerberosTime(der::string(et[7])); if (!endtime || now > endtime + Skew) refuse("the ticket expired"); if (et.count(6) && krb5::parseKerberosTime(der::string(et[6])) > now + Skew) refuse("the ticket isn't valid yet"); krb5::EncryptedData aenc = krb5::parseEncryptedData(ap[4]); Bytes aplain; try { aplain = krb5::decrypt(sessionKey, UsageApReqAuthenticator, aenc.cipher); } catch (const krb5::CryptoError &) { refuse("the authenticator doesn't decrypt"); } auto au = der::fields(der::application(aplain, 2)); if (!au.count(1) || !au.count(2) || !au.count(4) || !au.count(5)) refuse("incomplete authenticator"); if (der::string(au[1]) != crealm || au[2] != et[3]) refuse("authenticator and ticket name different clients"); std::string ctimeText = der::string(au[5]); time_t ctime = krb5::parseKerberosTime(ctimeText); long long cusec = der::integer(au[4]); long diff = (long)(now - ctime); if (!ctime || diff > Skew || diff < -Skew) refuse("the clocks differ by " + std::to_string(diff < 0 ? -diff : diff) + " s (more than 5 minutes)"); if (replayed(client.str() + "|" + ctimeText + "|" + std::to_string(cusec), now)) refuse("a replayed authenticator"); Accepted a; a.client = client; a.endtime = endtime; a.sessionKey = au.count(6) ? krb5::parseEncryptionKey(au[6]).value : sessionKey.value; /* the AP-REP: the authenticator's time back, under the ticket's session key */ uint8_t r[4]; netplus::fillRandomBytes(r, 4); uint32_t seq = ((uint32_t)r[0] << 24 | (uint32_t)r[1] << 16 | (uint32_t)r[2] << 8 | r[3]) & 0x3fffffff; Bytes encPart = ber::tlv(ber::applicationConstructed(27), ber::sequence(der::ctx(0, ber::tlv(0x18, ctimeText)) + der::ctx(1, ber::integer(cusec)) + der::ctx(3, ber::integer(seq)))); Bytes apRep = ber::tlv(ber::applicationConstructed(15), ber::sequence(der::ctx(0, ber::integer(5)) + der::ctx(1, ber::integer(15)) + der::ctx(2, der::encryptedData(sessionKey.etype, krb5::encrypt(sessionKey, UsageApRepEncPart, encPart))))); Bytes gss = ber::tlv(0x60, KrbOid + Bytes("\x02\x00", 2) + apRep); if (spnego) { /* NegTokenResp: accept-completed, the client's Kerberos mechanism, our token */ a.response = ber::tlv(0xa1, ber::sequence(der::ctx(0, ber::enumerated(0)) + der::ctx(1, oid) + der::ctx(2, ber::octets(gss)))); } else { a.response = gss; } return a; } catch (const ber::Error &e) { refuse(std::string("malformed token: ") + e.what()); } catch (const krb5::Error &e) { refuse(e.what()); } } } // namespace adconnect
src/acceptor.h 0 → 100644 +74 −0 Changes for src/acceptor.h: 74 added lines, 0 removed lines. Original line number Diff line number Diff line /******************************************************************************* Copyright (c) 2026, Jan Koester jan.koester@gmx.net All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ /* The accepting side of Kerberos, for SMB logins (ksmbd asks sharedd, * sharedd asks adconnectd): the client's token -- SPNEGO with a Kerberos * mechanism token, as Windows sends it, or a bare Kerberos GSS token -- * checked against our own account's keys: the ticket decrypted, the * authenticator fresh (5 minutes) and not seen before. The answer is the * client, the session key (the authenticator's subkey) and the token to * send back: the AP-REP, in SPNEGO if the client used SPNEGO. */ #pragma once #include <ctime> #include <functional> #include <map> #include <mutex> #include <stdexcept> #include <string> #include "krb5.h" namespace adconnect { struct Accepted { krb5::Principal client; krb5::Bytes sessionKey; krb5::Bytes response; time_t endtime = 0; /* the ticket's */ }; class Acceptor { public: /* our key of etype for a ticket of realm; false if we have none */ using KeySource = std::function<bool(const std::string &realm, int etype, krb5::Key &key)>; explicit Acceptor(KeySource keys) : _keys(std::move(keys)) {} /* throws std::runtime_error saying why the token isn't accepted */ Accepted accept(const krb5::Bytes &token); private: bool replayed(const std::string &key, time_t now); KeySource _keys; std::mutex _lock; std::map<std::string, time_t> _seen; /* authenticator -> until when it's remembered */ }; } // namespace adconnect
src/config.cpp +1 −0 Changes for src/config.cpp: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -257,6 +257,7 @@ void readDomain(const Scope &s, Config &cfg, bool multi, bool &idBaseSet) { readString(s, "home", cfg.home); readString(s, "shell", cfg.shell); readBool(s, "lowercase_names", cfg.lowercase); readBool(s, "create_home", cfg.createHome); cfg.qualified = multi; readBool(s, "qualified_names", cfg.qualified); readBool(s, "enumerate", cfg.enumerate); Loading
src/config.h +1 −0 Changes for src/config.h: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -75,6 +75,7 @@ struct Config { std::string home = "/home/%d/%n"; /* %d domain, %n the account name, %u the name (with @domain if qualified) */ std::string shell = "/bin/sh"; bool lowercase = true; bool createHome = true; /* make a missing home directory at a user's first lookup */ bool qualified = false; /* names user@domain (default with several domains) */ bool enumerate = false; std::string userFilter; /* ANDed to every user search */ Loading