Commit f827c082 authored by jan.koester's avatar jan.koester
Browse files

test

parent 4da35e9b
Loading
Loading
Loading
Loading
+6 −3
Changes for CMakeLists.txt: 6 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -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
@@ -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)
+147 −4
Changes for src/smb.cpp: 147 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -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>)
@@ -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