Commit 3b778221 authored by jan.koester's avatar jan.koester
Browse files

test

parent 8c71aa06
Loading
Loading
Loading
Loading
+149 −0
Changes for src/smb.cpp: 149 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -205,6 +205,27 @@ struct SpnegoAuthenResponseMsg {
    uint64_t session_expiry;
};

// ksmbd_ntlm_authen_request/response: a domain user's NTLM response, checked
// by its DC over NETLOGON (adconnectd); for AD users marked UserPassthrough
struct NtlmAuthenRequestMsg {
    uint32_t handle;
    unsigned char challenge[8];
    uint16_t user_name_len;
    uint16_t domain_name_len;
    uint16_t workstation_len;
    uint16_t nt_response_len;
    uint16_t lm_response_len;
    uint16_t reserved16;
    uint32_t reserved[16];
    // payload: user name, domain name, workstation (UTF16LE), NT, LM responses
};
struct NtlmAuthenResponseMsg {
    uint32_t handle;
    uint32_t status;  // UserOk or UserBadPassword
    unsigned char session_key[16];
    uint32_t reserved[16];
};

// the kernel accepts a response only at exactly its struct's size
static_assert(sizeof(StartupRequestMsg) == 744, "ksmbd_startup_request");
static_assert(sizeof(LoginRequestMsg) == 116, "ksmbd_login_request");
@@ -216,6 +237,8 @@ static_assert(sizeof(TreeConnectRequestMsg) == 264, "ksmbd_tree_connect_request"
static_assert(sizeof(TreeConnectResponseMsg) == 72, "ksmbd_tree_connect_response");
static_assert(sizeof(RpcCommandMsg) == 12, "ksmbd_rpc_command");
static_assert(sizeof(SpnegoAuthenResponseMsg) == 168, "ksmbd_spnego_authen_response");
static_assert(sizeof(NtlmAuthenRequestMsg) == 88, "ksmbd_ntlm_authen_request");
static_assert(sizeof(NtlmAuthenResponseMsg) == 88, "ksmbd_ntlm_authen_response");

enum : uint16_t {
    UserOk = 1 << 0,
@@ -650,6 +673,7 @@ void SmbServer::handle(int cmd, const unsigned char *payload, size_t len) {
        case TreeConnectRequest: treeConnect(payload, len); break;
        case RpcRequest: rpc(payload, len); break;
        case SpnegoAuthenRequest: spnego(payload, len); break;
        case NtlmAuthenRequest: ntlmAuthen(payload, len); break;
        default: break;  // heartbeat, tree disconnect, logout: nothing to answer
    }
}
@@ -681,6 +705,18 @@ void SmbServer::login(const unsigned char *payload, size_t len) {
        if (acct.idR || groupsOfR(acct).size() > 1) resp.status |= UserExtension;
#else
        if (groupsOf(acct.name.c_str(), acct.gid).size() > 1) resp.status |= UserExtension;
#endif
        copy(resp.account, sizeof(resp.account), acct.name);
    } else if (found && !root && !locked(acct.name.c_str()) && adconnectPresent()) {
        // an AD user has no local NT hash: tell ksmbd to pass the NTLM
        // response through to the domain controller (NTLM_AUTHEN_REQUEST)
        resp.uid = acct.uid;
        resp.gid = acct.gid;
        resp.status = UserOk | UserPassthrough;
#ifdef SHAREDD_UID_R
        if (acct.idR || groupsOfR(acct).size() > 1) resp.status |= UserExtension;
#else
        if (groupsOf(acct.name.c_str(), acct.gid).size() > 1) resp.status |= UserExtension;
#endif
        copy(resp.account, sizeof(resp.account), acct.name);
    } else if (!found && _cfg.settings.mapToGuest == "bad_user") {
@@ -938,6 +974,79 @@ bool askAdconnect(const unsigned char *blob, size_t len, KrbLogin &out, std::str
    return true;
}

bool adconnectPresent() {
    struct stat st;
    return stat(SHAREDD_ADCONNECT_SOCKET, &st) == 0 && S_ISSOCK(st.st_mode);
}

// a UTF-16LE name from the kernel as an 8-bit string (ASCII/Latin-1: the low
// byte of each unit -- AD account and domain names are effectively ASCII)
std::string utf16leLow(const unsigned char *p, size_t bytes) {
    std::string out;
    for (size_t i = 0; i + 1 < bytes; i += 2) out += (char)p[i];
    return out;
}

// have adconnectd check a domain user's NTLM response at the DC (NETLOGON).
// true with the 16-byte base session key on success; false if the password
// was refused or adconnectd couldn't be reached (*error set).
bool ntlmViaAdconnect(const std::string &domain, const std::string &account, const std::string &workstation,
                      const unsigned char challenge[8], const std::string &nt, const std::string &lm,
                      std::string &sessionKey, 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;
    }
    /* six uint16-length-prefixed fields: domain, account, workstation,
     * server challenge, NT response, LM response */
    std::string payload;
    auto fld = [&](const std::string &s) {
        payload += (char)(s.size() & 0xff);
        payload += (char)((s.size() >> 8) & 0xff);
        payload += s;
    };
    fld(domain);
    fld(account);
    fld(workstation);
    fld(std::string((const char *)challenge, 8));
    fld(nt);
    fld(lm);
    std::string req = "NTLM " + base64((const unsigned char *)payload.data(), payload.size()) + "\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;
    }
    /* N <status> <base64 session key> */
    size_t nl = reply.find('\n');
    if (reply.compare(0, 2, "N\t") != 0 || nl == std::string::npos) {
        error = "bad answer from adconnectd";
        return false;
    }
    std::vector<std::string> f;
    std::string line = reply.substr(2, nl - 2);
    size_t p = 0;
    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() != 2) { error = "bad answer from adconnectd"; return false; }
    unsigned long status = strtoul(f[0].c_str(), nullptr, 10);
    if (status != 0) { error = "the domain controller refused the password"; return false; }
    if (!unbase64(f[1], sessionKey)) { error = "bad answer from adconnectd"; return false; }
    return true;
}

} // namespace

// Kerberos logins (domain members asking for cifs/<this system>): the
@@ -997,4 +1106,44 @@ void SmbServer::spnego(const unsigned char *payload, size_t len) {
    send(SpnegoAuthenResponse, msg.data(), msg.size(), false);
}

// NTLM logins of domain users (no Kerberos): ksmbd forwards the client's
// response here for an account marked UserPassthrough; adconnectd has its
// domain controller check it over NETLOGON and returns the session key.
void SmbServer::ntlmAuthen(const unsigned char *payload, size_t len) {
    NtlmAuthenResponseMsg resp;
    memset(&resp, 0, sizeof(resp));
    resp.status = UserBadPassword;
    if (len < sizeof(NtlmAuthenRequestMsg)) {
        send(NtlmAuthenResponse, &resp, sizeof(resp), false);
        return;
    }
    NtlmAuthenRequestMsg req;
    memcpy(&req, payload, sizeof(req));
    resp.handle = req.handle;
    size_t off = sizeof(NtlmAuthenRequestMsg);
    size_t need = (size_t)req.user_name_len + req.domain_name_len + req.workstation_len +
                  req.nt_response_len + req.lm_response_len;
    if (off + need > len) {
        send(NtlmAuthenResponse, &resp, sizeof(resp), false);
        return;
    }
    const unsigned char *p = payload + off;
    std::string user = utf16leLow(p, req.user_name_len); p += req.user_name_len;
    std::string domain = utf16leLow(p, req.domain_name_len); p += req.domain_name_len;
    std::string ws = utf16leLow(p, req.workstation_len); p += req.workstation_len;
    std::string nt((const char *)p, req.nt_response_len); p += req.nt_response_len;
    std::string lm((const char *)p, req.lm_response_len); p += req.lm_response_len;

    std::string key, error;
    if (ntlmViaAdconnect(domain, user, ws, req.challenge, nt, lm, key, error)) {
        resp.status = UserOk;
        memcpy(resp.session_key, key.data(), std::min<size_t>(key.size(), sizeof(resp.session_key)));
        std::fill(key.begin(), key.end(), '\0');
    } else {
        std::cerr << "sharedd: SMB: NTLM login for " << domain << "\\" << user << " refused: " << error
                  << std::endl;
    }
    send(NtlmAuthenResponse, &resp, sizeof(resp), false);
}

} // namespace sharedd