/* Editor Settings: expandtabs and use 4 spaces for indentation
* ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: *
*/
/*
* Copyright Likewise Software 2004-2008
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details. You should have received a copy of the GNU General
* Public License along with this program. If not, see
* .
*
* LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING
* TERMS AS WELL. IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT
* WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE
* TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU
* GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE. IF YOU
* HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING
* TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT
* license@likewisesoftware.com
*/
/*
* Copyright (C) Likewise Software. All rights reserved.
*
* Module Name:
*
* join.c
*
* Abstract:
*
* Likewise Security and Authentication Subsystem (LSASS)
*
* Join to Active Directory
*
* Authors: Krishna Ganugapati (krishnag@likewisesoftware.com)
* Sriram Nambakam (snambakam@likewisesoftware.com)
*/
#include "includes.h"
#define LSA_JOIN_OU_PREFIX "OU="
#define LSA_JOIN_CN_PREFIX "CN="
#define LSA_JOIN_DC_PREFIX "DC="
#define LSA_JOIN_MAX_ALLOWED_CLOCK_DRIFT_SECONDS 60
DWORD
LsaNetJoinDomain(
PCSTR pszHostname,
PCSTR pszHostDnsDomain,
PCSTR pszDomain,
PCSTR pszOU,
PCSTR pszUsername,
PCSTR pszPassword,
PCSTR pszOSName,
PCSTR pszOSVersion,
PCSTR pszOSServicePack,
DWORD dwFlags
)
{
DWORD dwError = 0;
PSTR pszOU_DN = NULL;
PWSTR pwszHostname = NULL;
PWSTR pwszHostDnsDomain = NULL;
PWSTR pwszDomain = NULL;
PWSTR pwszOU = NULL;
PWSTR pwszOSName = NULL;
PWSTR pwszOSVersion = NULL;
PWSTR pwszOSServicePack = NULL;
DWORD dwOptions = (NETSETUP_JOIN_DOMAIN |
NETSETUP_ACCT_CREATE |
NETSETUP_DOMAIN_JOIN_IF_JOINED);
PLSA_CREDS_FREE_INFO pAccessInfo = NULL;
BAIL_ON_INVALID_STRING(pszHostname);
BAIL_ON_INVALID_STRING(pszDomain);
BAIL_ON_INVALID_STRING(pszUsername);
if (geteuid() != 0) {
dwError = EACCES;
BAIL_ON_LSA_ERROR(dwError);
}
if ( !(dwFlags & LSA_NET_JOIN_DOMAIN_NOTIMESYNC) )
{
dwError = LsaSyncTimeToDC(pszDomain);
BAIL_ON_LSA_ERROR(dwError);
}
dwError = LsaSetSMBCreds(
pszDomain,
pszUsername,
pszPassword,
TRUE,
&pAccessInfo,
NULL);
BAIL_ON_LSA_ERROR(dwError);
dwError = LsaMbsToWc16s(
pszHostname,
&pwszHostname);
BAIL_ON_LSA_ERROR(dwError);
if (!LW_IS_NULL_OR_EMPTY_STR(pszHostDnsDomain))
{
dwError = LsaMbsToWc16s(
pszHostDnsDomain,
&pwszHostDnsDomain);
BAIL_ON_LSA_ERROR(dwError);
}
dwError = LsaMbsToWc16s(
pszDomain,
&pwszDomain);
BAIL_ON_LSA_ERROR(dwError);
if (!LW_IS_NULL_OR_EMPTY_STR(pszOU)) {
dwError = LsaBuildOrgUnitDN(
pszDomain,
pszOU,
&pszOU_DN);
BAIL_ON_LSA_ERROR(dwError);
dwError = LsaMbsToWc16s(
pszOU_DN,
&pwszOU);
BAIL_ON_LSA_ERROR(dwError);
}
if (!LW_IS_NULL_OR_EMPTY_STR(pszOSName)) {
dwError = LsaMbsToWc16s(
pszOSName,
&pwszOSName);
BAIL_ON_LSA_ERROR(dwError);
}
if (!LW_IS_NULL_OR_EMPTY_STR(pszOSVersion)) {
dwError = LsaMbsToWc16s(
pszOSVersion,
&pwszOSVersion);
BAIL_ON_LSA_ERROR(dwError);
}
if (!LW_IS_NULL_OR_EMPTY_STR(pszOSServicePack)) {
dwError = LsaMbsToWc16s(
pszOSServicePack,
&pwszOSServicePack);
BAIL_ON_LSA_ERROR(dwError);
}
dwError = NetJoinDomainLocal(
pwszHostname,
pwszHostDnsDomain,
pwszDomain,
pwszOU,
NULL,
NULL,
dwOptions,
pwszOSName,
pwszOSVersion,
pwszOSServicePack);
BAIL_ON_LSA_ERROR(dwError);
cleanup:
LsaFreeSMBCreds(&pAccessInfo);
LW_SAFE_FREE_STRING(pszOU_DN);
LW_SAFE_FREE_MEMORY(pwszHostname);
LW_SAFE_FREE_MEMORY(pwszHostDnsDomain);
LW_SAFE_FREE_MEMORY(pwszDomain);
LW_SAFE_FREE_MEMORY(pwszOU);
LW_SAFE_FREE_MEMORY(pwszOSName);
LW_SAFE_FREE_MEMORY(pwszOSVersion);
LW_SAFE_FREE_MEMORY(pwszOSServicePack);
return dwError;
error:
goto cleanup;
}
DWORD
LsaBuildOrgUnitDN(
PCSTR pszDomain,
PCSTR pszOU,
PSTR* ppszOU_DN
)
{
DWORD dwError = 0;
PSTR pszOuDN = NULL;
// Do not free
PSTR pszOutputPos = NULL;
PCSTR pszInputPos = NULL;
PCSTR pszInputSectionEnd = NULL;
size_t sOutputDnLen = 0;
size_t sSectionLen = 0;
DWORD nDomainParts = 0;
BAIL_ON_INVALID_STRING(pszDomain);
BAIL_ON_INVALID_STRING(pszOU);
// Figure out the length required to write the OU DN
pszInputPos = pszOU;
// skip leading slashes
sSectionLen = strspn(pszInputPos, "/");
pszInputPos += sSectionLen;
while ((sSectionLen = strcspn(pszInputPos, "/")) != 0) {
sOutputDnLen += sizeof(LSA_JOIN_OU_PREFIX) - 1;
sOutputDnLen += sSectionLen;
// For the separating comma
sOutputDnLen++;
pszInputPos += sSectionLen;
sSectionLen = strspn(pszInputPos, "/");
pszInputPos += sSectionLen;
}
// Figure out the length required to write the Domain DN
pszInputPos = pszDomain;
while ((sSectionLen = strcspn(pszInputPos, ".")) != 0) {
sOutputDnLen += sizeof(LSA_JOIN_DC_PREFIX) - 1;
sOutputDnLen += sSectionLen;
nDomainParts++;
pszInputPos += sSectionLen;
sSectionLen = strspn(pszInputPos, ".");
pszInputPos += sSectionLen;
}
// Add in space for the separating commas
if (nDomainParts > 1)
{
sOutputDnLen += nDomainParts - 1;
}
dwError = LwAllocateMemory(
sizeof(CHAR) * (sOutputDnLen + 1),
(PVOID*)&pszOuDN);
BAIL_ON_LSA_ERROR(dwError);
pszOutputPos = pszOuDN;
// Iterate through pszOU backwards and write to pszOuDN forwards
pszInputPos = pszOU + strlen(pszOU) - 1;
while(TRUE)
{
// strip trailing slashes
while (pszInputPos >= pszOU && *pszInputPos == '/')
{
pszInputPos--;
}
if (pszInputPos < pszOU)
{
break;
}
// Find the end of this section (so that we can copy it to
// the output string in forward order).
pszInputSectionEnd = pszInputPos;
while (pszInputPos >= pszOU && *pszInputPos != '/')
{
pszInputPos--;
}
sSectionLen = pszInputSectionEnd - pszInputPos;
// Only "Computers" as the first element is a CN.
if ((pszOutputPos == pszOuDN) &&
(sSectionLen == sizeof("Computers") - 1) &&
!strncasecmp(pszInputPos + 1, "Computers", sizeof("Computers") - 1))
{
// Add CN=,
memcpy(pszOutputPos, LSA_JOIN_CN_PREFIX,
sizeof(LSA_JOIN_CN_PREFIX) - 1);
pszOutputPos += sizeof(LSA_JOIN_CN_PREFIX) - 1;
}
else
{
// Add OU=,
memcpy(pszOutputPos, LSA_JOIN_OU_PREFIX,
sizeof(LSA_JOIN_OU_PREFIX) - 1);
pszOutputPos += sizeof(LSA_JOIN_OU_PREFIX) - 1;
}
memcpy(pszOutputPos,
pszInputPos + 1,
sSectionLen);
pszOutputPos += sSectionLen;
*pszOutputPos++ = ',';
}
// Make sure to overwrite any initial "CN=Computers" as "OU=Computers".
// Note that it is safe to always set "OU=" as the start of the DN
// unless the DN so far is exacly "CN=Computers,".
if (strcasecmp(pszOuDN, LSA_JOIN_CN_PREFIX "Computers,"))
{
memcpy(pszOuDN, LSA_JOIN_OU_PREFIX, sizeof(LSA_JOIN_OU_PREFIX) - 1);
}
// Read the domain name foward in sections and write it back out
// forward.
pszInputPos = pszDomain;
while (TRUE)
{
sSectionLen = strcspn(pszInputPos, ".");
memcpy(pszOutputPos,
LSA_JOIN_DC_PREFIX,
sizeof(LSA_JOIN_DC_PREFIX) - 1);
pszOutputPos += sizeof(LSA_JOIN_DC_PREFIX) - 1;
memcpy(pszOutputPos, pszInputPos, sSectionLen);
pszOutputPos += sSectionLen;
pszInputPos += sSectionLen;
sSectionLen = strspn(pszInputPos, ".");
pszInputPos += sSectionLen;
if (*pszInputPos != 0)
{
// Add a comma for the next entry
*pszOutputPos++ = ',';
}
else
break;
}
assert(pszOutputPos == pszOuDN + sizeof(CHAR) * (sOutputDnLen));
*pszOutputPos = 0;
*ppszOU_DN = pszOuDN;
cleanup:
return dwError;
error:
*ppszOU_DN = NULL;
LW_SAFE_FREE_STRING(pszOuDN);
goto cleanup;
}
DWORD
LsaNetTestJoinDomain(
PBOOLEAN pbIsJoined
)
{
DWORD dwError = 0;
BOOLEAN bIsJoined = FALSE;
HANDLE hStore = (HANDLE)NULL;
PLWPS_PASSWORD_INFO pPassInfo = NULL;
PSTR pszHostname = NULL;
dwError = LsaDnsGetHostInfo(&pszHostname);
BAIL_ON_LSA_ERROR(dwError);
dwError = LwpsOpenPasswordStore(
LWPS_PASSWORD_STORE_DEFAULT,
&hStore);
BAIL_ON_LSA_ERROR(dwError);
dwError = LwpsGetPasswordByHostName(
hStore,
pszHostname,
&pPassInfo);
switch(dwError)
{
case LWPS_ERROR_INVALID_ACCOUNT:
bIsJoined = FALSE;
dwError = 0;
break;
case 0:
bIsJoined = TRUE;
break;
default:
BAIL_ON_LSA_ERROR(dwError);
}
*pbIsJoined = bIsJoined;
cleanup:
if (pPassInfo) {
LwpsFreePasswordInfo(hStore, pPassInfo);
}
if (hStore != (HANDLE)NULL) {
LwpsClosePasswordStore(hStore);
}
LW_SAFE_FREE_STRING(pszHostname);
return dwError;
error:
*pbIsJoined = FALSE;
goto cleanup;
}
VOID
LsaEnableDebugLog(
VOID
)
{
NetEnableDebug();
}
VOID
LsaDisableDebugLog(
VOID
)
{
NetDisableDebug();
}
DWORD
LsaSyncTimeToDC(
PCSTR pszDomain
)
{
DWORD dwError = 0;
LWNET_UNIX_TIME_T dcTime = 0;
time_t ttDCTime = 0;
dwError = LWNetGetDCTime(
pszDomain,
&dcTime);
BAIL_ON_LSA_ERROR(dwError);
ttDCTime = (time_t) dcTime;
if (labs(ttDCTime - time(NULL)) > LSA_JOIN_MAX_ALLOWED_CLOCK_DRIFT_SECONDS) {
dwError = LwSetSystemTime(ttDCTime);
BAIL_ON_LSA_ERROR(dwError);
}
cleanup:
return dwError;
error:
goto cleanup;
}
DWORD
LsaBuildMachineAccountInfo(
PLWPS_PASSWORD_INFO pInfo,
PLSA_MACHINE_ACCT_INFO* ppAcctInfo
)
{
DWORD dwError = 0;
PLSA_MACHINE_ACCT_INFO pAcctInfo = NULL;
dwError = LwAllocateMemory(
sizeof(LSA_MACHINE_ACCT_INFO),
(PVOID*)&pAcctInfo);
BAIL_ON_LSA_ERROR(dwError);
dwError = LsaWc16sToMbs(
pInfo->pwszDnsDomainName,
&pAcctInfo->pszDnsDomainName);
BAIL_ON_LSA_ERROR(dwError);
dwError = LsaWc16sToMbs(
pInfo->pwszDomainName,
&pAcctInfo->pszDomainName);
BAIL_ON_LSA_ERROR(dwError);
dwError = LsaWc16sToMbs(
pInfo->pwszHostname,
&pAcctInfo->pszHostname);
BAIL_ON_LSA_ERROR(dwError);
dwError = LsaWc16sToMbs(
pInfo->pwszMachineAccount,
&pAcctInfo->pszMachineAccount);
BAIL_ON_LSA_ERROR(dwError);
dwError = LsaWc16sToMbs(
pInfo->pwszMachinePassword,
&pAcctInfo->pszMachinePassword);
BAIL_ON_LSA_ERROR(dwError);
dwError = LsaWc16sToMbs(
pInfo->pwszSID,
&pAcctInfo->pszSID);
BAIL_ON_LSA_ERROR(dwError);
pAcctInfo->dwSchannelType = pInfo->dwSchannelType;
pAcctInfo->last_change_time = pInfo->last_change_time;
*ppAcctInfo = pAcctInfo;
cleanup:
return dwError;
error:
*ppAcctInfo = NULL;
if (pAcctInfo) {
LsaFreeMachineAccountInfo(pAcctInfo);
}
goto cleanup;
}
VOID
LsaFreeMachineAccountInfo(
PLSA_MACHINE_ACCT_INFO pAcctInfo
)
{
LW_SAFE_FREE_STRING(pAcctInfo->pszDnsDomainName);
LW_SAFE_FREE_STRING(pAcctInfo->pszDomainName);
LW_SAFE_FREE_STRING(pAcctInfo->pszHostname);
LW_SAFE_FREE_STRING(pAcctInfo->pszMachineAccount);
LW_SAFE_FREE_STRING(pAcctInfo->pszMachinePassword);
LW_SAFE_FREE_STRING(pAcctInfo->pszSID);
LwFreeMemory(pAcctInfo);
}
DWORD
LsaEnableDomainGroupMembership(
PCSTR pszDomainName
)
{
return LsaChangeDomainGroupMembership(pszDomainName,
TRUE);
}
DWORD
LsaChangeDomainGroupMembership(
IN PCSTR pszDomainName,
IN BOOLEAN bEnable
)
{
DWORD dwError = ERROR_SUCCESS;
NTSTATUS ntStatus = STATUS_SUCCESS;
RPCSTATUS rpcStatus = RPC_S_OK;
handle_t hLsaBinding = NULL;
WCHAR wszLocalSystem[] = { '\\', '\\', '\0' };
PWSTR pwszSystem = wszLocalSystem;
DWORD dwLocalPolicyAccessMask = LSA_ACCESS_VIEW_POLICY_INFO;
POLICY_HANDLE hLocalPolicy = NULL;
LsaPolicyInformation *pInfo = NULL;
PSID pDomainSid = NULL;
handle_t hSamrBinding = NULL;
DWORD dwLocalSamrAccessMask = SAMR_ACCESS_ENUM_DOMAINS |
SAMR_ACCESS_OPEN_DOMAIN;
SamrConnectInfo ConnReq;
DWORD dwConnReqLevel = 0;
SamrConnectInfo ConnInfo;
DWORD dwConnLevel = 0;
CONNECT_HANDLE hSamrConn = NULL;
DWORD dwBuiltinDomainAccessMask = DOMAIN_ACCESS_OPEN_ACCOUNT;
PSID pBuiltinDomainSid = NULL;
DOMAIN_HANDLE hBuiltinDomain = NULL;
DWORD dwAliasAccessMask = ALIAS_ACCESS_ADD_MEMBER |
ALIAS_ACCESS_REMOVE_MEMBER;
ACCOUNT_HANDLE hAlias = NULL;
PSID pDomainAdminsSid = NULL;
PSID pDomainUsersSid = NULL;
DWORD iGroup = 0;
DWORD iMember = 0;
PSID *pppAdminMembers[] = { &pDomainAdminsSid, NULL };
PSID *pppUsersMembers[] = { &pDomainUsersSid, NULL };
struct _LOCAL_GROUP_MEMBERS
{
DWORD dwLocalGroupRid;
PSID **pppMembers;
}
Memberships[] =
{
{ DOMAIN_ALIAS_RID_ADMINS,
pppAdminMembers
},
{ DOMAIN_ALIAS_RID_USERS,
pppUsersMembers
}
};
memset(&ConnReq, 0, sizeof(ConnReq));
memset(&ConnInfo, 0, sizeof(ConnInfo));
/*
* Connect local lsa rpc server and get basic
* domain information
*/
rpcStatus = InitLsaBindingDefault(&hLsaBinding,
NULL,
NULL);
if (rpcStatus)
{
ntStatus = LwRpcStatusToNtStatus(rpcStatus);
}
ntStatus = LsaOpenPolicy2(hLsaBinding,
pwszSystem,
NULL,
dwLocalPolicyAccessMask,
&hLocalPolicy);
BAIL_ON_NT_STATUS(ntStatus);
ntStatus = LsaQueryInfoPolicy2(hLsaBinding,
hLocalPolicy,
LSA_POLICY_INFO_DOMAIN,
&pInfo);
BAIL_ON_NT_STATUS(ntStatus);
pDomainSid = pInfo->domain.sid;
dwError = LwCreateWellKnownSid(WinAccountDomainAdminsSid,
pDomainSid,
&pDomainAdminsSid,
NULL);
BAIL_ON_LSA_ERROR(dwError);
dwError = LwCreateWellKnownSid(WinAccountDomainUsersSid,
pDomainSid,
&pDomainUsersSid,
NULL);
BAIL_ON_LSA_ERROR(dwError);
/*
* Connect local samr rpc server and open BUILTIN domain
*/
rpcStatus = InitSamrBindingDefault(&hSamrBinding,
NULL,
NULL);
if (rpcStatus)
{
ntStatus = LwRpcStatusToNtStatus(rpcStatus);
}
ConnReq.info1.client_version = SAMR_CONNECT_POST_WIN2K;
dwConnReqLevel = 1;
ntStatus = SamrConnect5(hSamrBinding,
pwszSystem,
dwLocalSamrAccessMask,
dwConnReqLevel,
&ConnReq,
&dwConnLevel,
&ConnInfo,
&hSamrConn);
BAIL_ON_NT_STATUS(ntStatus);
dwError = LwCreateWellKnownSid(WinBuiltinDomainSid,
NULL,
&pBuiltinDomainSid,
NULL);
BAIL_ON_LSA_ERROR(dwError);
ntStatus = SamrOpenDomain(hSamrBinding,
hSamrConn,
dwBuiltinDomainAccessMask,
pBuiltinDomainSid,
&hBuiltinDomain);
BAIL_ON_NT_STATUS(ntStatus);
/*
* Add requested domain groups or users to their
* corresponding local groups
*/
for (iGroup = 0;
iGroup < sizeof(Memberships)/sizeof(Memberships[0]);
iGroup++)
{
DWORD dwRid = Memberships[iGroup].dwLocalGroupRid;
ntStatus = SamrOpenAlias(hSamrBinding,
hBuiltinDomain,
dwAliasAccessMask,
dwRid,
&hAlias);
BAIL_ON_NT_STATUS(ntStatus);
for (iMember = 0;
Memberships[iGroup].pppMembers[iMember] != NULL;
iMember++)
{
PSID *ppSid = Memberships[iGroup].pppMembers[iMember];
if (bEnable)
{
ntStatus = SamrAddAliasMember(hSamrBinding,
hAlias,
(*ppSid));
if (ntStatus == STATUS_MEMBER_IN_ALIAS)
{
ntStatus = STATUS_SUCCESS;
}
}
else
{
ntStatus = SamrDeleteAliasMember(hSamrBinding,
hAlias,
(*ppSid));
if (ntStatus == STATUS_MEMBER_NOT_IN_ALIAS)
{
ntStatus = STATUS_SUCCESS;
}
}
BAIL_ON_NT_STATUS(ntStatus);
}
ntStatus = SamrClose(hSamrBinding, hAlias);
BAIL_ON_NT_STATUS(ntStatus);
hAlias = NULL;
}
cleanup:
if (hSamrBinding && hAlias)
{
SamrClose(hSamrBinding, hAlias);
}
if (hSamrBinding && hBuiltinDomain)
{
SamrClose(hSamrBinding, hBuiltinDomain);
}
if (hSamrBinding && hSamrConn)
{
SamrClose(hSamrBinding, hSamrConn);
}
if (pInfo)
{
LsaRpcFreeMemory(pInfo);
}
if (hLsaBinding && hLocalPolicy)
{
LsaClose(hLsaBinding, hLocalPolicy);
}
FreeSamrBinding(&hSamrBinding);
FreeLsaBinding(&hLsaBinding);
LW_SAFE_FREE_MEMORY(pBuiltinDomainSid);
LW_SAFE_FREE_MEMORY(pDomainAdminsSid);
LW_SAFE_FREE_MEMORY(pDomainUsersSid);
if (dwError == ERROR_SUCCESS &&
ntStatus != STATUS_SUCCESS)
{
dwError = LwNtStatusToWin32Error(ntStatus);
}
return dwError;
error:
goto cleanup;
}
/*
local variables:
mode: c
c-basic-offset: 4
indent-tabs-mode: nil
tab-width: 4
end:
*/