Loading CMakeLists.txt +6 −3 Changes for CMakeLists.txt: 6 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -9,8 +9,9 @@ project(sharedd VERSION 0.1.0 LANGUAGES CXX) # (libconfplus) and offers them on sysbus as the service "sharedd". # Users come from the libc: getpwnam(), getgrouplist(), getspnam() and, # for SMB's NTLM, the NT hash shadow.yaml keeps next to the password # (shadow_get_nthash(), set with shadow_set_password()). root never logs # in over SMB. # (shadow_get_nthash(), set with shadow_set_password()). Kerberos logins # (Active Directory users on domain members) are checked by adconnectd, # which holds the domain's computer account. root never logs in over SMB. # # sharedd the daemon (root) # sharectl command line over sysbus Loading Loading @@ -38,7 +39,9 @@ add_executable(sharedd src/iscsi.cpp ) target_include_directories(sharedd PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src) target_compile_definitions(sharedd PRIVATE SHAREDD_CONFIG="${SHAREDD_CONFIG}") set(SHAREDD_ADCONNECT_SOCKET "/run/adconnect.sock" CACHE FILEPATH "adconnectd's socket, for Kerberos logins") target_compile_definitions(sharedd PRIVATE SHAREDD_CONFIG="${SHAREDD_CONFIG}" SHAREDD_ADCONNECT_SOCKET="${SHAREDD_ADCONNECT_SOCKET}") target_link_libraries(sharedd PRIVATE confplus::confplus sysbus::sysbus) add_executable(sharectl src/sharectl.cpp) Loading src/smb.cpp +147 −4 Changes for src/smb.cpp: 147 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -42,6 +42,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <pwd.h> #include <shadow.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/un.h> #include <unistd.h> #if __has_include(<sys/uid_r.h>) Loading Loading @@ -840,15 +842,156 @@ void SmbServer::rpc(const unsigned char *payload, size_t len) { send(RpcResponse, msg.data(), msg.size(), false); } namespace { const char B64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; std::string base64(const unsigned char *p, size_t n) { std::string out; size_t i = 0; for (; i + 2 < n; i += 3) { uint32_t v = (uint32_t)p[i] << 16 | (uint32_t)p[i + 1] << 8 | p[i + 2]; out += B64[v >> 18]; out += B64[v >> 12 & 63]; out += B64[v >> 6 & 63]; out += B64[v & 63]; } if (n - i == 1) { uint32_t v = (uint32_t)p[i] << 16; out += B64[v >> 18]; out += B64[v >> 12 & 63]; out += "=="; } else if (n - i == 2) { uint32_t v = (uint32_t)p[i] << 16 | (uint32_t)p[i + 1] << 8; out += B64[v >> 18]; out += B64[v >> 12 & 63]; out += B64[v >> 6 & 63]; out += '='; } return out; } bool unbase64(const std::string &text, std::string &out) { out.clear(); uint32_t v = 0; int bits = 0; for (char c : text) { if (c == '=') break; const char *p = strchr(B64, c); if (!c || !p) return false; v = v << 6 | (uint32_t)(p - B64); bits += 6; if (bits >= 8) { bits -= 8; out += (char)(v >> bits & 0xff); } } return true; } struct KrbLogin { std::string name, sessionKey, reply; uint64_t endtime = 0; }; /* adconnectd checks the client's Kerberos token (SPNEGO from the SMB * session setup) against the domain's computer account: the user, the * session key and the token for the client. false and *error otherwise * (adconnect not running: no Kerberos, the client tries NTLM) */ bool askAdconnect(const unsigned char *blob, size_t len, KrbLogin &out, std::string &error) { int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); if (fd < 0) { error = strerror(errno); return false; } sockaddr_un a{}; a.sun_family = AF_UNIX; snprintf(a.sun_path, sizeof a.sun_path, "%s", SHAREDD_ADCONNECT_SOCKET); struct timeval tv = {30, 0}; setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv); if (connect(fd, (sockaddr *)&a, sizeof a) != 0) { error = std::string("adconnectd isn't running (") + strerror(errno) + ")"; close(fd); return false; } std::string req = "KRB5 " + base64(blob, len) + "\n", reply; bool ok = ::send(fd, req.data(), req.size(), MSG_NOSIGNAL) == (ssize_t)req.size(); char buf[8192]; for (ssize_t n; ok && (n = recv(fd, buf, sizeof buf, 0)) > 0;) reply.append(buf, (size_t)n); close(fd); if (reply.compare(0, 4, "ERR ") == 0) { error = reply.substr(4, reply.find('\n') - 4); return false; } /* K <name> <key> <reply> <end> */ std::vector<std::string> f; size_t p = 0, nl = reply.find('\n'); if (reply.compare(0, 2, "K\t") != 0 || nl == std::string::npos) { error = "bad answer from adconnectd"; return false; } std::string line = reply.substr(2, nl - 2); for (size_t q; (q = line.find('\t', p)) != std::string::npos; p = q + 1) f.push_back(line.substr(p, q - p)); f.push_back(line.substr(p)); if (f.size() != 4 || !unbase64(f[1], out.sessionKey) || !unbase64(f[2], out.reply)) { error = "bad answer from adconnectd"; return false; } out.name = f[0]; out.endtime = strtoull(f[3].c_str(), nullptr, 10); return true; } } // namespace // Kerberos logins (domain members asking for cifs/<this system>): the // token goes to adconnectd, which holds the computer account's keys void SmbServer::spnego(const unsigned char *payload, size_t len) { // Kerberos isn't supported: a failed login, clients fall back to NTLM if (len < sizeof(uint32_t)) return; if (len < 6) return; uint32_t handle; uint16_t blobLen; memcpy(&handle, payload, 4); memcpy(&blobLen, payload + 4, 2); SpnegoAuthenResponseMsg resp; memset(&resp, 0, sizeof(resp)); memcpy(&resp.handle, payload, sizeof(uint32_t)); resp.login_response.handle = resp.handle; resp.handle = handle; resp.login_response.handle = handle; resp.login_response.status = UserBadUser; KrbLogin k; std::string error; Account acct; bool ok = blobLen <= len - 6 && askAdconnect(payload + 6, blobLen, k, error); if (ok && !lookupAccount(k.name, acct)) { error = "no such user " + k.name; ok = false; } if (ok && ((!acct.idR && acct.uid == 0) || locked(acct.name.c_str()))) { error = acct.name + (locked(acct.name.c_str()) ? ": account locked" : ": root may not log in over SMB"); ok = false; } // the kernel's session key buffer (CIFS_KEY_SIZE) if (ok && (k.sessionKey.size() > 40 || k.reply.size() > 0xffff)) { error = "session key or reply too long"; ok = false; } if (!ok) { std::cerr << "sharedd: SMB: Kerberos login refused: " << error << std::endl; send(SpnegoAuthenResponse, &resp, sizeof(resp), false); return; } resp.login_response.uid = acct.uid; resp.login_response.gid = acct.gid; resp.login_response.status = UserOk; #ifdef SHAREDD_UID_R if (acct.idR || groupsOfR(acct).size() > 1) resp.login_response.status |= UserExtension; #else if (groupsOf(acct.name.c_str(), acct.gid).size() > 1) resp.login_response.status |= UserExtension; #endif copy(resp.login_response.account, sizeof(resp.login_response.account), acct.name); resp.session_key_len = (uint16_t)k.sessionKey.size(); resp.spnego_blob_len = (uint16_t)k.reply.size(); resp.session_expiry = k.endtime; std::vector<unsigned char> msg(sizeof(resp) + k.sessionKey.size() + k.reply.size()); memcpy(msg.data(), &resp, sizeof(resp)); memcpy(msg.data() + sizeof(resp), k.sessionKey.data(), k.sessionKey.size()); memcpy(msg.data() + sizeof(resp) + k.sessionKey.size(), k.reply.data(), k.reply.size()); std::fill(k.sessionKey.begin(), k.sessionKey.end(), '\0'); endpwent(); send(SpnegoAuthenResponse, msg.data(), msg.size(), false); } } // namespace sharedd Loading
CMakeLists.txt +6 −3 Changes for CMakeLists.txt: 6 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -9,8 +9,9 @@ project(sharedd VERSION 0.1.0 LANGUAGES CXX) # (libconfplus) and offers them on sysbus as the service "sharedd". # Users come from the libc: getpwnam(), getgrouplist(), getspnam() and, # for SMB's NTLM, the NT hash shadow.yaml keeps next to the password # (shadow_get_nthash(), set with shadow_set_password()). root never logs # in over SMB. # (shadow_get_nthash(), set with shadow_set_password()). Kerberos logins # (Active Directory users on domain members) are checked by adconnectd, # which holds the domain's computer account. root never logs in over SMB. # # sharedd the daemon (root) # sharectl command line over sysbus Loading Loading @@ -38,7 +39,9 @@ add_executable(sharedd src/iscsi.cpp ) target_include_directories(sharedd PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src) target_compile_definitions(sharedd PRIVATE SHAREDD_CONFIG="${SHAREDD_CONFIG}") set(SHAREDD_ADCONNECT_SOCKET "/run/adconnect.sock" CACHE FILEPATH "adconnectd's socket, for Kerberos logins") target_compile_definitions(sharedd PRIVATE SHAREDD_CONFIG="${SHAREDD_CONFIG}" SHAREDD_ADCONNECT_SOCKET="${SHAREDD_ADCONNECT_SOCKET}") target_link_libraries(sharedd PRIVATE confplus::confplus sysbus::sysbus) add_executable(sharectl src/sharectl.cpp) Loading
src/smb.cpp +147 −4 Changes for src/smb.cpp: 147 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -42,6 +42,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <pwd.h> #include <shadow.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/un.h> #include <unistd.h> #if __has_include(<sys/uid_r.h>) Loading Loading @@ -840,15 +842,156 @@ void SmbServer::rpc(const unsigned char *payload, size_t len) { send(RpcResponse, msg.data(), msg.size(), false); } namespace { const char B64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; std::string base64(const unsigned char *p, size_t n) { std::string out; size_t i = 0; for (; i + 2 < n; i += 3) { uint32_t v = (uint32_t)p[i] << 16 | (uint32_t)p[i + 1] << 8 | p[i + 2]; out += B64[v >> 18]; out += B64[v >> 12 & 63]; out += B64[v >> 6 & 63]; out += B64[v & 63]; } if (n - i == 1) { uint32_t v = (uint32_t)p[i] << 16; out += B64[v >> 18]; out += B64[v >> 12 & 63]; out += "=="; } else if (n - i == 2) { uint32_t v = (uint32_t)p[i] << 16 | (uint32_t)p[i + 1] << 8; out += B64[v >> 18]; out += B64[v >> 12 & 63]; out += B64[v >> 6 & 63]; out += '='; } return out; } bool unbase64(const std::string &text, std::string &out) { out.clear(); uint32_t v = 0; int bits = 0; for (char c : text) { if (c == '=') break; const char *p = strchr(B64, c); if (!c || !p) return false; v = v << 6 | (uint32_t)(p - B64); bits += 6; if (bits >= 8) { bits -= 8; out += (char)(v >> bits & 0xff); } } return true; } struct KrbLogin { std::string name, sessionKey, reply; uint64_t endtime = 0; }; /* adconnectd checks the client's Kerberos token (SPNEGO from the SMB * session setup) against the domain's computer account: the user, the * session key and the token for the client. false and *error otherwise * (adconnect not running: no Kerberos, the client tries NTLM) */ bool askAdconnect(const unsigned char *blob, size_t len, KrbLogin &out, std::string &error) { int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); if (fd < 0) { error = strerror(errno); return false; } sockaddr_un a{}; a.sun_family = AF_UNIX; snprintf(a.sun_path, sizeof a.sun_path, "%s", SHAREDD_ADCONNECT_SOCKET); struct timeval tv = {30, 0}; setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv); if (connect(fd, (sockaddr *)&a, sizeof a) != 0) { error = std::string("adconnectd isn't running (") + strerror(errno) + ")"; close(fd); return false; } std::string req = "KRB5 " + base64(blob, len) + "\n", reply; bool ok = ::send(fd, req.data(), req.size(), MSG_NOSIGNAL) == (ssize_t)req.size(); char buf[8192]; for (ssize_t n; ok && (n = recv(fd, buf, sizeof buf, 0)) > 0;) reply.append(buf, (size_t)n); close(fd); if (reply.compare(0, 4, "ERR ") == 0) { error = reply.substr(4, reply.find('\n') - 4); return false; } /* K <name> <key> <reply> <end> */ std::vector<std::string> f; size_t p = 0, nl = reply.find('\n'); if (reply.compare(0, 2, "K\t") != 0 || nl == std::string::npos) { error = "bad answer from adconnectd"; return false; } std::string line = reply.substr(2, nl - 2); for (size_t q; (q = line.find('\t', p)) != std::string::npos; p = q + 1) f.push_back(line.substr(p, q - p)); f.push_back(line.substr(p)); if (f.size() != 4 || !unbase64(f[1], out.sessionKey) || !unbase64(f[2], out.reply)) { error = "bad answer from adconnectd"; return false; } out.name = f[0]; out.endtime = strtoull(f[3].c_str(), nullptr, 10); return true; } } // namespace // Kerberos logins (domain members asking for cifs/<this system>): the // token goes to adconnectd, which holds the computer account's keys void SmbServer::spnego(const unsigned char *payload, size_t len) { // Kerberos isn't supported: a failed login, clients fall back to NTLM if (len < sizeof(uint32_t)) return; if (len < 6) return; uint32_t handle; uint16_t blobLen; memcpy(&handle, payload, 4); memcpy(&blobLen, payload + 4, 2); SpnegoAuthenResponseMsg resp; memset(&resp, 0, sizeof(resp)); memcpy(&resp.handle, payload, sizeof(uint32_t)); resp.login_response.handle = resp.handle; resp.handle = handle; resp.login_response.handle = handle; resp.login_response.status = UserBadUser; KrbLogin k; std::string error; Account acct; bool ok = blobLen <= len - 6 && askAdconnect(payload + 6, blobLen, k, error); if (ok && !lookupAccount(k.name, acct)) { error = "no such user " + k.name; ok = false; } if (ok && ((!acct.idR && acct.uid == 0) || locked(acct.name.c_str()))) { error = acct.name + (locked(acct.name.c_str()) ? ": account locked" : ": root may not log in over SMB"); ok = false; } // the kernel's session key buffer (CIFS_KEY_SIZE) if (ok && (k.sessionKey.size() > 40 || k.reply.size() > 0xffff)) { error = "session key or reply too long"; ok = false; } if (!ok) { std::cerr << "sharedd: SMB: Kerberos login refused: " << error << std::endl; send(SpnegoAuthenResponse, &resp, sizeof(resp), false); return; } resp.login_response.uid = acct.uid; resp.login_response.gid = acct.gid; resp.login_response.status = UserOk; #ifdef SHAREDD_UID_R if (acct.idR || groupsOfR(acct).size() > 1) resp.login_response.status |= UserExtension; #else if (groupsOf(acct.name.c_str(), acct.gid).size() > 1) resp.login_response.status |= UserExtension; #endif copy(resp.login_response.account, sizeof(resp.login_response.account), acct.name); resp.session_key_len = (uint16_t)k.sessionKey.size(); resp.spnego_blob_len = (uint16_t)k.reply.size(); resp.session_expiry = k.endtime; std::vector<unsigned char> msg(sizeof(resp) + k.sessionKey.size() + k.reply.size()); memcpy(msg.data(), &resp, sizeof(resp)); memcpy(msg.data() + sizeof(resp), k.sessionKey.data(), k.sessionKey.size()); memcpy(msg.data() + sizeof(resp) + k.sessionKey.size(), k.reply.data(), k.reply.size()); std::fill(k.sessionKey.begin(), k.sessionKey.end(), '\0'); endpwent(); send(SpnegoAuthenResponse, msg.data(), msg.size(), false); } } // namespace sharedd