#include <stdio.h>
#include <strings.h>
#include <stdlib.h>
#include <time.h>
#include <alloca.h>
#include <unistd.h>
#include <netdb.h>
#include <thread.h>
#include <libmlrpc/libmlrpc.h>
#include <smbsrv/libsmb.h>
#include <smbsrv/libmlsvc.h>
#include <smbsrv/ndl/netlogon.ndl>
#include <smbsrv/netrauth.h>
#include <smbsrv/smbinfo.h>
#include <smbsrv/smb_token.h>
#include <mlsvc.h>
uint32_t netlogon_logon(smb_logon_t *, smb_token_t *, smb_domainex_t *);
static uint32_t netr_server_samlogon(mlsvc_handle_t *, netr_info_t *, char *,
smb_logon_t *, smb_token_t *);
static void netr_invalidate_chain(netr_info_t *);
static void netr_interactive_samlogon(netr_info_t *, smb_logon_t *,
struct netr_logon_info1 *);
static void netr_network_samlogon(ndr_heap_t *, netr_info_t *,
smb_logon_t *, struct netr_logon_info2 *);
static void netr_setup_identity(ndr_heap_t *, smb_logon_t *,
netr_logon_id_t *);
static uint32_t netr_setup_domain_groups(struct netr_validation_info3 *,
smb_ids_t *);
static uint32_t netr_setup_krb5res_groups(struct krb5_validation_info *,
smb_ids_t *);
static uint32_t netr_setup_token_wingrps(struct netr_validation_info3 *,
smb_token_t *);
extern netr_info_t netr_global_info;
static mutex_t netlogon_mutex;
static cond_t netlogon_cv;
static boolean_t netlogon_busy = B_FALSE;
static boolean_t netlogon_abort = B_FALSE;
uint32_t
smb_decode_krb5_pac(smb_token_t *token, char *data, uint_t len)
{
struct krb5_validation_info info;
ndr_buf_t *nbuf;
smb_sid_t *domsid;
uint32_t status = NT_STATUS_NO_MEMORY;
int rc;
bzero(&info, sizeof (info));
nbuf = ndr_buf_init(&TYPEINFO(netr_interface));
if (nbuf == NULL)
goto out;
rc = ndr_buf_decode(nbuf, NDR_PTYPE_PAC,
NETR_OPNUM_decode_krb5_pac, data, len, &info);
if (rc != NDR_DRC_OK) {
status = RPC_NT_PROTOCOL_ERROR;
goto out;
}
domsid = (smb_sid_t *)info.info3.LogonDomainId;
token->tkn_user.i_sid = smb_sid_splice(domsid,
info.info3.UserId);
if (token->tkn_user.i_sid == NULL)
goto out;
token->tkn_primary_grp.i_sid = smb_sid_splice(domsid,
info.info3.PrimaryGroupId);
if (token->tkn_primary_grp.i_sid == NULL)
goto out;
if (info.info3.EffectiveName.str) {
token->tkn_account_name =
strdup((char *)info.info3.EffectiveName.str);
if (token->tkn_account_name == NULL)
goto out;
}
if (info.info3.LogonDomainName.str) {
token->tkn_domain_name =
strdup((char *)info.info3.LogonDomainName.str);
if (token->tkn_domain_name == NULL)
goto out;
}
status = netr_setup_domain_groups(&info.info3, &token->tkn_win_grps);
if (status != NT_STATUS_SUCCESS)
goto out;
if (info.rg_rid_cnt != 0) {
status = netr_setup_krb5res_groups(&info, &token->tkn_win_grps);
if (status != NT_STATUS_SUCCESS)
goto out;
}
status = netr_setup_token_wingrps(&info.info3, token);
out:
if (nbuf != NULL)
ndr_buf_fini(nbuf);
return (status);
}
void
smb_logon_abort(void)
{
(void) mutex_lock(&netlogon_mutex);
if (netlogon_busy && !netlogon_abort)
syslog(LOG_DEBUG, "logon abort");
netlogon_abort = B_TRUE;
(void) cond_broadcast(&netlogon_cv);
(void) mutex_unlock(&netlogon_mutex);
}
int smb_netr_logon_retries = 3;
void
smb_logon_domain(smb_logon_t *user_info, smb_token_t *token)
{
smb_domainex_t di;
uint32_t status;
int retries = smb_netr_logon_retries;
if (user_info->lg_secmode != SMB_SECMODE_DOMAIN)
return;
if (user_info->lg_domain_type == SMB_DOMAIN_LOCAL)
return;
while (--retries > 0) {
if (!smb_domain_getinfo(&di)) {
syslog(LOG_ERR, "logon DC getinfo failed");
status = NT_STATUS_NO_LOGON_SERVERS;
goto out;
}
(void) mutex_lock(&netlogon_mutex);
while (netlogon_busy && !netlogon_abort)
(void) cond_wait(&netlogon_cv, &netlogon_mutex);
if (netlogon_abort) {
(void) mutex_unlock(&netlogon_mutex);
status = NT_STATUS_REQUEST_ABORTED;
goto out;
}
netlogon_busy = B_TRUE;
(void) mutex_unlock(&netlogon_mutex);
status = netlogon_logon(user_info, token, &di);
(void) mutex_lock(&netlogon_mutex);
netlogon_busy = B_FALSE;
if (netlogon_abort)
status = NT_STATUS_REQUEST_ABORTED;
(void) cond_signal(&netlogon_cv);
(void) mutex_unlock(&netlogon_mutex);
switch (status) {
case NT_STATUS_BAD_NETWORK_PATH:
case NT_STATUS_BAD_NETWORK_NAME:
case RPC_NT_SERVER_TOO_BUSY:
status = NT_STATUS_NO_LOGON_SERVERS;
break;
default:
goto out;
}
}
out:
if (status != NT_STATUS_SUCCESS)
syslog(LOG_INFO, "logon[%s\\%s]: %s", user_info->lg_e_domain,
user_info->lg_e_username, xlate_nt_status(status));
user_info->lg_status = status;
}
static uint32_t
netr_get_handle(char *server, char *domain, mlsvc_handle_t *netr_handle)
{
uint32_t status;
boolean_t did_renego = B_FALSE;
reauth:
if ((netr_global_info.flags & NETR_FLG_VALID) == 0 ||
!smb_match_netlogon_seqnum()) {
status = netlogon_auth(server, domain, NETR_FLG_NULL);
if (status != 0) {
syslog(LOG_ERR, "%s: auth failed (%s)",
__func__, xlate_nt_status(status));
return (status);
}
netr_global_info.flags |= NETR_FLG_VALID;
}
status = netr_open_secure(server, domain, netr_handle);
if (status != 0) {
if (!did_renego) {
did_renego = B_TRUE;
netr_invalidate_chain(&netr_global_info);
syslog(LOG_ERR, "%s: open failed (%s); "
"renegotiating...",
__func__, xlate_nt_status(status));
goto reauth;
}
syslog(LOG_ERR, "%s: open failed (%s)",
__func__, xlate_nt_status(status));
}
return (status);
}
uint32_t
netlogon_logon(smb_logon_t *user_info, smb_token_t *token, smb_domainex_t *di)
{
char server[MAXHOSTNAMELEN];
mlsvc_handle_t netr_handle;
uint32_t status;
boolean_t did_reauth = B_FALSE;
if (di->d_dci.dc_name[0] != '\0' &&
(*netr_global_info.server != '\0')) {
(void) snprintf(server, sizeof (server),
"\\\\%s", di->d_dci.dc_name);
if (strncasecmp(netr_global_info.server,
server, strlen(server)) != 0)
netr_invalidate_chain(&netr_global_info);
}
reauth:
status = netr_get_handle(di->d_dci.dc_name,
di->d_primary.di_nbname, &netr_handle);
if (status != 0) {
syslog(LOG_ERR, "%s: failed to get handle (%s)",
__func__, xlate_nt_status(status));
return (NT_STATUS_DOMAIN_TRUST_INCONSISTENT);
}
status = netr_server_samlogon(&netr_handle,
&netr_global_info, di->d_dci.dc_name, user_info, token);
if (status == NT_STATUS_INSUFFICIENT_LOGON_INFO) {
if (!did_reauth) {
(void) netr_close(&netr_handle);
did_reauth = B_TRUE;
goto reauth;
}
status = NT_STATUS_DOMAIN_TRUST_INCONSISTENT;
}
(void) netr_close(&netr_handle);
return (status);
}
uint32_t
smb_netlogon_check(char *server, char *domain)
{
mlsvc_handle_t netr_handle;
uint32_t status;
(void) mutex_lock(&netlogon_mutex);
while (netlogon_busy)
(void) cond_wait(&netlogon_cv, &netlogon_mutex);
netlogon_busy = B_TRUE;
(void) mutex_unlock(&netlogon_mutex);
status = netr_get_handle(server, domain, &netr_handle);
if (status == 0)
(void) netr_close(&netr_handle);
else
syslog(LOG_ERR, "%s: failed to get handle (%s)",
__func__, xlate_nt_status(status));
(void) mutex_lock(&netlogon_mutex);
netlogon_busy = B_FALSE;
(void) cond_signal(&netlogon_cv);
(void) mutex_unlock(&netlogon_mutex);
return (status);
}
static uint32_t
netr_setup_token(struct netr_validation_info3 *info3, smb_logon_t *user_info,
netr_info_t *netr_info, smb_token_t *token)
{
char *username, *domain;
unsigned char rc4key[SMBAUTH_SESSION_KEY_SZ];
smb_sid_t *domsid;
uint32_t status;
char nbdomain[NETBIOS_NAME_SZ];
domsid = (smb_sid_t *)info3->LogonDomainId;
token->tkn_user.i_sid = smb_sid_splice(domsid, info3->UserId);
if (token->tkn_user.i_sid == NULL)
return (NT_STATUS_NO_MEMORY);
token->tkn_primary_grp.i_sid = smb_sid_splice(domsid,
info3->PrimaryGroupId);
if (token->tkn_primary_grp.i_sid == NULL)
return (NT_STATUS_NO_MEMORY);
username = (info3->EffectiveName.str)
? (char *)info3->EffectiveName.str : user_info->lg_e_username;
if (info3->LogonDomainName.str) {
domain = (char *)info3->LogonDomainName.str;
} else if (*user_info->lg_e_domain != '\0') {
domain = user_info->lg_e_domain;
} else {
(void) smb_getdomainname(nbdomain, sizeof (nbdomain));
domain = nbdomain;
}
if (username)
token->tkn_account_name = strdup(username);
if (domain)
token->tkn_domain_name = strdup(domain);
if (token->tkn_account_name == NULL || token->tkn_domain_name == NULL)
return (NT_STATUS_NO_MEMORY);
status = netr_setup_domain_groups(info3, &token->tkn_win_grps);
if (status != NT_STATUS_SUCCESS)
return (status);
status = netr_setup_token_wingrps(info3, token);
if (status != NT_STATUS_SUCCESS)
return (status);
if ((token->tkn_ssnkey.val = malloc(SMBAUTH_SESSION_KEY_SZ)) == NULL)
return (NT_STATUS_NO_MEMORY);
token->tkn_ssnkey.len = SMBAUTH_SESSION_KEY_SZ;
bzero(rc4key, SMBAUTH_SESSION_KEY_SZ);
bcopy(netr_info->session_key.key, rc4key, netr_info->session_key.len);
bcopy(info3->UserSessionKey.data, token->tkn_ssnkey.val,
SMBAUTH_SESSION_KEY_SZ);
rand_hash((unsigned char *)token->tkn_ssnkey.val,
SMBAUTH_SESSION_KEY_SZ, rc4key, SMBAUTH_SESSION_KEY_SZ);
return (NT_STATUS_SUCCESS);
}
uint32_t
netr_server_samlogon(mlsvc_handle_t *netr_handle, netr_info_t *netr_info,
char *server, smb_logon_t *user_info, smb_token_t *token)
{
struct netr_SamLogon logon_op;
struct netr_SamLogonEx logon_ex_op;
struct netr_authenticator auth;
struct netr_authenticator ret_auth;
struct netr_logon_info1 info1;
struct netr_logon_info2 info2;
struct netr_validation_info3 *info3;
union netr_validation_u *valid_info;
union netr_logon_info_u *logon_info;
LPTSTR servername, hostname;
ndr_heap_t *heap;
int opnum;
int rc, len;
uint32_t status, *rpc_status;
void *rpc_arg;
len = strlen(server) + 4;
servername = ndr_rpc_malloc(netr_handle, len);
hostname = ndr_rpc_malloc(netr_handle, NETBIOS_NAME_SZ);
if (servername == NULL || hostname == NULL) {
ndr_rpc_release(netr_handle);
return (NT_STATUS_INTERNAL_ERROR);
}
(void) snprintf((char *)servername, len, "\\\\%s", server);
if (smb_getnetbiosname((char *)hostname, NETBIOS_NAME_SZ) != 0) {
ndr_rpc_release(netr_handle);
return (NT_STATUS_INTERNAL_ERROR);
}
rc = netr_setup_authenticator(netr_info, &auth, &ret_auth);
if (rc != SMBAUTH_SUCCESS) {
ndr_rpc_release(netr_handle);
return (NT_STATUS_INTERNAL_ERROR);
}
if (netr_info->use_logon_ex &&
(netr_info->nego_flags & NETR_NEGO_SECURE_RPC_FLAG) != 0) {
bzero(&logon_ex_op, sizeof (struct netr_SamLogonEx));
logon_ex_op.servername = servername;
logon_ex_op.hostname = hostname;
logon_ex_op.logon_info.logon_level = user_info->lg_level;
logon_ex_op.logon_info.switch_value = user_info->lg_level;
logon_ex_op.validation_level = NETR_VALIDATION_LEVEL3;
logon_ex_op.extra_flags = 0;
logon_info = &logon_ex_op.logon_info.ru;
valid_info = &logon_ex_op.ru;
rpc_status = &logon_ex_op.status;
rpc_arg = &logon_ex_op;
opnum = NETR_OPNUM_SamLogonEx;
} else {
bzero(&logon_op, sizeof (struct netr_SamLogon));
logon_op.servername = servername;
logon_op.hostname = hostname;
logon_op.auth = &auth;
logon_op.ret_auth = &ret_auth;
logon_op.logon_info.logon_level = user_info->lg_level;
logon_op.logon_info.switch_value = user_info->lg_level;
logon_op.validation_level = NETR_VALIDATION_LEVEL3;
logon_info = &logon_op.logon_info.ru;
valid_info = &logon_op.ru;
rpc_status = &logon_op.status;
rpc_arg = &logon_op;
opnum = NETR_OPNUM_SamLogon;
}
heap = ndr_rpc_get_heap(netr_handle);
switch (user_info->lg_level) {
case NETR_INTERACTIVE_LOGON:
netr_setup_identity(heap, user_info, &info1.identity);
netr_interactive_samlogon(netr_info, user_info, &info1);
logon_info->info1 = &info1;
break;
case NETR_NETWORK_LOGON:
if (user_info->lg_challenge_key.len < 8 ||
user_info->lg_challenge_key.val == NULL) {
ndr_rpc_release(netr_handle);
return (NT_STATUS_INVALID_PARAMETER);
}
netr_setup_identity(heap, user_info, &info2.identity);
netr_network_samlogon(heap, netr_info, user_info, &info2);
logon_info->info2 = &info2;
break;
default:
ndr_rpc_release(netr_handle);
return (NT_STATUS_INVALID_PARAMETER);
}
rc = ndr_rpc_call(netr_handle, opnum, rpc_arg);
if (rc != 0) {
netr_invalidate_chain(netr_info);
status = NT_STATUS_INVALID_PARAMETER;
} else if (*rpc_status != 0) {
status = NT_SC_VALUE(*rpc_status);
if (rpc_arg == &logon_op)
(void) netr_validate_chain(netr_info,
logon_op.ret_auth);
} else {
if (rpc_arg == &logon_op) {
status = netr_validate_chain(netr_info,
logon_op.ret_auth);
if (status == NT_STATUS_INSUFFICIENT_LOGON_INFO) {
ndr_rpc_release(netr_handle);
return (status);
}
}
info3 = valid_info->info3;
status = netr_setup_token(info3, user_info, netr_info, token);
}
ndr_rpc_release(netr_handle);
return (status);
}
static void
netr_interactive_samlogon(netr_info_t *netr_info, smb_logon_t *user_info,
struct netr_logon_info1 *info1)
{
BYTE key[NETR_OWF_PASSWORD_SZ];
(void) memcpy(&info1->lm_owf_password,
user_info->lg_lm_password.val, sizeof (netr_owf_password_t));
(void) memcpy(&info1->nt_owf_password,
user_info->lg_nt_password.val, sizeof (netr_owf_password_t));
(void) memset(key, 0, NETR_OWF_PASSWORD_SZ);
(void) memcpy(key, netr_info->session_key.key,
netr_info->session_key.len);
rand_hash((unsigned char *)&info1->lm_owf_password,
NETR_OWF_PASSWORD_SZ, key, NETR_OWF_PASSWORD_SZ);
rand_hash((unsigned char *)&info1->nt_owf_password,
NETR_OWF_PASSWORD_SZ, key, NETR_OWF_PASSWORD_SZ);
}
static void
netr_network_samlogon(ndr_heap_t *heap, netr_info_t *netr_info,
smb_logon_t *user_info, struct netr_logon_info2 *info2)
{
uint32_t len;
if (user_info->lg_challenge_key.len >= 8 &&
user_info->lg_challenge_key.val != 0) {
bcopy(user_info->lg_challenge_key.val,
info2->lm_challenge.data, 8);
} else {
bzero(info2->lm_challenge.data, 8);
}
if ((len = user_info->lg_nt_password.len) != 0) {
ndr_heap_mkvcb(heap, user_info->lg_nt_password.val, len,
(ndr_vcbuf_t *)&info2->nt_response);
} else {
bzero(&info2->nt_response, sizeof (netr_vcbuf_t));
}
if ((len = user_info->lg_lm_password.len) != 0) {
ndr_heap_mkvcb(heap, user_info->lg_lm_password.val, len,
(ndr_vcbuf_t *)&info2->lm_response);
} else {
bzero(&info2->lm_response, sizeof (netr_vcbuf_t));
}
}
int
netr_setup_authenticator(netr_info_t *netr_info,
struct netr_authenticator *auth, struct netr_authenticator *ret_auth)
{
int rc;
bzero(auth, sizeof (struct netr_authenticator));
netr_info->timestamp = time(0) - 1;
do {
auth->timestamp = ++netr_info->timestamp;
rc = netr_gen_credentials(netr_info->session_key.key,
&netr_info->client_credential,
netr_info->timestamp,
(netr_cred_t *)&auth->credential, B_TRUE);
if (rc != SMBAUTH_SUCCESS && rc != SMBAUTH_RETRY)
return (SMBAUTH_FAILURE);
} while (rc == SMBAUTH_RETRY);
if (ret_auth) {
bzero(ret_auth, sizeof (struct netr_authenticator));
ret_auth->timestamp = netr_info->timestamp;
}
return (SMBAUTH_SUCCESS);
}
uint32_t
netr_validate_chain(netr_info_t *netr_info, struct netr_authenticator *auth)
{
netr_cred_t cred;
uint32_t result = NT_STATUS_SUCCESS;
uint32_t *dwp;
++netr_info->timestamp;
if (netr_gen_credentials(netr_info->session_key.key,
&netr_info->client_credential,
netr_info->timestamp, &cred, B_FALSE) != SMBAUTH_SUCCESS)
return (NT_STATUS_INTERNAL_ERROR);
if (auth == NULL) {
netr_invalidate_chain(netr_info);
return (NT_STATUS_INSUFFICIENT_LOGON_INFO);
}
result = memcmp(&cred, &auth->credential, sizeof (netr_cred_t));
if (result != 0) {
netr_invalidate_chain(netr_info);
result = NT_STATUS_UNSUCCESSFUL;
} else {
dwp = (uint32_t *)&netr_info->client_credential;
dwp[0] += netr_info->timestamp;
netr_info->flags |= NETR_FLG_VALID;
}
return (result);
}
static void
netr_invalidate_chain(netr_info_t *netr_info)
{
if ((netr_info->flags & NETR_FLG_VALID) == 0)
return;
netr_info->flags &= ~NETR_FLG_VALID;
explicit_bzero(&netr_info->session_key,
sizeof (netr_info->session_key));
explicit_bzero(&netr_info->rpc_seal_key,
sizeof (netr_info->rpc_seal_key));
explicit_bzero(&netr_info->client_credential,
sizeof (netr_info->client_credential));
explicit_bzero(&netr_info->server_credential,
sizeof (netr_info->server_credential));
}
static void
netr_setup_identity(ndr_heap_t *heap, smb_logon_t *user_info,
netr_logon_id_t *identity)
{
static mutex_t logon_id_mutex;
static uint32_t logon_id;
(void) mutex_lock(&logon_id_mutex);
if (logon_id == 0)
logon_id = 0xDCD0;
++logon_id;
user_info->lg_logon_id = logon_id;
(void) mutex_unlock(&logon_id_mutex);
identity->parameter_control =
MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT |
MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT;
identity->logon_id.LowPart = logon_id;
identity->logon_id.HighPart = 0;
ndr_heap_mkvcs(heap, user_info->lg_domain,
(ndr_vcstr_t *)&identity->domain_name);
ndr_heap_mkvcs(heap, user_info->lg_username,
(ndr_vcstr_t *)&identity->username);
ndr_heap_mkvcs(heap, user_info->lg_workstation,
(ndr_vcstr_t *)&identity->workstation);
}
static uint32_t
netr_setup_token_wingrps(struct netr_validation_info3 *info3 __unused,
smb_token_t *token)
{
uint32_t status;
status = smb_sam_usr_groups(token->tkn_user.i_sid,
&token->tkn_win_grps);
if (status != NT_STATUS_SUCCESS)
return (status);
status = smb_wka_token_groups(token->tkn_flags, &token->tkn_win_grps);
return (status);
}
static uint32_t
netr_setup_domain_groups(struct netr_validation_info3 *info3, smb_ids_t *gids)
{
smb_sid_t *domain_sid;
smb_id_t *ids;
int i, total_cnt;
if ((i = info3->GroupCount) == 0)
i++;
i += info3->SidCount;
total_cnt = gids->i_cnt + i;
gids->i_ids = realloc(gids->i_ids, total_cnt * sizeof (smb_id_t));
if (gids->i_ids == NULL)
return (NT_STATUS_NO_MEMORY);
domain_sid = (smb_sid_t *)info3->LogonDomainId;
ids = gids->i_ids + gids->i_cnt;
for (i = 0; i < info3->GroupCount; i++, gids->i_cnt++, ids++) {
ids->i_sid = smb_sid_splice(domain_sid, info3->GroupIds[i].rid);
if (ids->i_sid == NULL)
return (NT_STATUS_NO_MEMORY);
ids->i_attrs = info3->GroupIds[i].attributes;
}
if (info3->GroupCount == 0) {
ids->i_sid = smb_sid_splice(domain_sid, info3->PrimaryGroupId);
if (ids->i_sid == NULL)
return (NT_STATUS_NO_MEMORY);
ids->i_attrs = 0x7;
gids->i_cnt++;
ids++;
}
for (i = 0; i < info3->SidCount; i++, gids->i_cnt++, ids++) {
ids->i_sid = smb_sid_dup((smb_sid_t *)info3->ExtraSids[i].sid);
if (ids->i_sid == NULL)
return (NT_STATUS_NO_MEMORY);
ids->i_attrs = info3->ExtraSids[i].attributes;
}
return (NT_STATUS_SUCCESS);
}
static uint32_t netr_setup_krb5res_groups(struct krb5_validation_info *info,
smb_ids_t *gids)
{
smb_sid_t *domain_sid;
smb_id_t *ids;
int i, total_cnt;
total_cnt = gids->i_cnt + info->rg_rid_cnt;
gids->i_ids = realloc(gids->i_ids, total_cnt * sizeof (smb_id_t));
if (gids->i_ids == NULL)
return (NT_STATUS_NO_MEMORY);
domain_sid = (smb_sid_t *)info->rg_dom_sid;
ids = gids->i_ids + gids->i_cnt;
for (i = 0; i < info->rg_rid_cnt; i++, gids->i_cnt++, ids++) {
ids->i_sid = smb_sid_splice(domain_sid, info->rg_rids[i].rid);
if (ids->i_sid == NULL)
return (NT_STATUS_NO_MEMORY);
ids->i_attrs = info->rg_rids[i].attributes;
}
return (0);
}