Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1653 lines
42 KiB

/*++
Copyright (c) 1991 Microsoft Corporation
Module Name:
MSAM.C
Abstract:
Contains mapping functions to present netcmd with non-unicode
view of SAM/LSA. We also package the data in a simpler form for
the netcmd side to deal with.
Author:
ChuckC 13-Apr-1992
Environment:
User Mode - Win32
Revision History:
13-Apr-1992 chuckc Created.
--*/
#include <nt.h> // base definitions
#include <ntsam.h> // for Sam***
#include <ntlsa.h> // for Lsa***
#include <ntrtl.h> // for RtlGetNtProductType()
#include <nturtl.h> // allows <windows.h> to compile. since we've
// already included NT, and <winnt.h> will not
// be picked up, and <winbase.h> needs these defs.
#include <windows.h>
#include <lmcons.h>
#include <lmerr.h>
#include <netlib.h> // NetpNtStatusToApistatus
#include <netlib0.h> // str*f
#include <netlibnt.h> // NetpNtStatusToApistatus
#include <secobj.h> // NetpGetBuiltInDomainSID
#include <apperr.h>
#include "port1632.h"
#include <string.h>
#include <tchar.h>
#include "netascii.h"
/*
* globals. init to NULL, closed before app exits.
*/
SAM_HANDLE BuiltInDomainHandle = NULL;
SAM_HANDLE AccountsDomainHandle = NULL;
SAM_HANDLE AliasHandle = NULL;
LSA_HANDLE LsaHandle = NULL;
PSID AccountDomainSid = NULL ;
/*
* forward declare various worker functions. more detailed
* descriptions are found with function bodies.
*/
// enumerate aliases in a domain. returns NERR_* or APE_*
USHORT EnumerateAliases(SAM_HANDLE DomainHandle,
ALIAS_ENTRY **ppAlias,
ULONG *pcAlias,
ULONG *pcMaxEntry) ;
// From a RID & Domain, create the PSID. Returns NERR_* or APE_*
USHORT MSamGetSIDFromRID(PSID pSidDomain,
ULONG rid,
PSID *ppSID) ;
// From a name, lookup the PSID. Returns NERR_* or APE_*
USHORT MSamGetSIDFromName(TCHAR *name,
PSID *ppSID) ;
// From a name, lookup the RSID. Returns NERR_* or APE_*
USHORT MSamGetRIDFromName(TCHAR *name,
ULONG *pRID) ;
// From a SID, lookup the ASCII name.
USHORT MSamGetNameFromSID(PSID psid,
TCHAR **name) ;
// check if a SID (of particular type is already in a domain
BOOL MSamCheckIfExists(PUNICODE_STRING pAccount,
SAM_HANDLE hDomain,
SID_NAME_USE use) ;
USHORT MCreateUnicodeString(TCHAR *pch, PUNICODE_STRING pUnicodeStr);
/*------------------------- SAM operations ---------------------------*/
/*
* MOpenSAM
*
* here we go thru all the steps needed to get a SAM domain handles
* so we can go do our thing. we also get the LSA handle while we're here.
*
* return code: NERR_Success if got handle.
* error code that can be used for ErrorExit() otherwise
* parameters: server indicates the machine to perform operation on.
* priv is a netcmd defined number that indicates what we
* need to do with the database.
*/
USHORT MOpenSAM(TCHAR *server, ULONG priv)
{
SAM_HANDLE ServerHandle = NULL;
OBJECT_ATTRIBUTES ObjectAttributes;
PPOLICY_ACCOUNT_DOMAIN_INFO PolicyAccountDomainInfo = NULL;
NTSTATUS NtStatus;
USHORT err = NERR_Success ;
DWORD sidlength ;
UNICODE_STRING unistrServer ;
ACCESS_MASK ServerAccessMask, AccountDomainAccessMask,
BuiltInDomainAccessMask, LsaAccessMask ;
unistrServer.Length = 0 ;
unistrServer.Length = 2 ;
unistrServer.Buffer = L"" ;
err = MCreateUnicodeString(server, &unistrServer) ;
if (err != NERR_Success)
return(err) ;
/*
* figure out the right access mask
*/
switch(priv)
{
case READ_PRIV:
ServerAccessMask = SAM_SERVER_READ | SAM_SERVER_EXECUTE ;
AccountDomainAccessMask = DOMAIN_READ | DOMAIN_EXECUTE ;
BuiltInDomainAccessMask = DOMAIN_READ | DOMAIN_EXECUTE ;
LsaAccessMask = POLICY_EXECUTE ;
break ;
case WRITE_PRIV:
ServerAccessMask = SAM_SERVER_READ |
SAM_SERVER_EXECUTE ;
AccountDomainAccessMask = DOMAIN_READ |
DOMAIN_EXECUTE |
DOMAIN_CREATE_ALIAS ;
BuiltInDomainAccessMask = DOMAIN_READ |
DOMAIN_EXECUTE ;
LsaAccessMask = POLICY_EXECUTE ;
break ;
default:
// currently, no other supported
ServerAccessMask = 0 ;
AccountDomainAccessMask = 0 ;
LsaAccessMask = 0 ;
return(ERROR_INVALID_PARAMETER) ;
}
/*
* Open the LSA
*/
InitializeObjectAttributes( &ObjectAttributes, NULL, 0, 0, NULL );
NtStatus = LsaOpenPolicy(&unistrServer,
&ObjectAttributes,
LsaAccessMask,
&LsaHandle);
if (!NT_SUCCESS(NtStatus))
{
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
goto error_exit ;
}
/*
* Get the Account domain SID from LSA
*/
NtStatus = LsaQueryInformationPolicy(LsaHandle,
PolicyAccountDomainInformation,
(PVOID *)&PolicyAccountDomainInfo);
if (!NT_SUCCESS(NtStatus))
{
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
goto error_exit ;
}
/*
* Connect to SAM
*/
InitializeObjectAttributes( &ObjectAttributes, NULL, 0, 0, NULL );
NtStatus = SamConnect(
&unistrServer,
&ServerHandle,
ServerAccessMask,
&ObjectAttributes
);
if (!NT_SUCCESS(NtStatus))
{
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
goto error_exit ;
}
/*
* Make copy of Account Domain Sid. We later use this to determine
* if an account retrieved is in the Account Domain or not.
*/
sidlength = (ULONG) GetLengthSid(PolicyAccountDomainInfo->DomainSid) ;
if (err = MAllocMem(sidlength, (CHAR **)&AccountDomainSid))
goto error_exit ;
if (!CopySid(sidlength,
AccountDomainSid,
PolicyAccountDomainInfo->DomainSid))
{
err = (USHORT)GetLastError() ;
goto error_exit ;
}
/*
* Open the Account Domain.
*/
NtStatus = SamOpenDomain(
ServerHandle,
AccountDomainAccessMask,
PolicyAccountDomainInfo->DomainSid,
&AccountsDomainHandle
);
if (!NT_SUCCESS(NtStatus))
{
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
goto error_exit ;
}
/*
* Open the builtin domain
*/
// Create well-known SIDs. we are only interested in BuiltIn Domain
if (! NT_SUCCESS (NtStatus = NetpCreateWellKnownSids(NULL)))
{
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
goto error_exit ;
}
// open the built in domain
NtStatus = SamOpenDomain(
ServerHandle,
BuiltInDomainAccessMask,
BuiltinDomainSid, // setup by NetpCreateWellKnownSids
&BuiltInDomainHandle
);
if (!NT_SUCCESS(NtStatus))
{
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
goto error_exit ;
}
err = NERR_Success ;
goto ok_exit ;
error_exit:
/*
* only get here if we failed. clean everything up and go home.
*/
if (BuiltInDomainHandle)
{
SamCloseHandle(BuiltInDomainHandle);
BuiltInDomainHandle = NULL;
}
if (AccountsDomainHandle)
{
SamCloseHandle(AccountsDomainHandle);
AccountsDomainHandle = NULL;
}
if (LsaHandle)
{
LsaClose(LsaHandle);
LsaHandle = NULL;
}
if (AccountDomainSid)
{
MFreeMem((CHAR *)AccountDomainSid);
AccountDomainSid = NULL ;
}
ok_exit:
/*
* successful exit point. clean up the transient pieces and go home.
*/
if (PolicyAccountDomainInfo)
{
LsaFreeMemory(PolicyAccountDomainInfo);
PolicyAccountDomainInfo = NULL ;
}
if (ServerHandle)
{
SamCloseHandle(ServerHandle);
ServerHandle = NULL ;
}
return(err) ;
}
/*
* MCloseSAM
*
* close the the various handles
*
* return code: none
* parameters: none
*/
VOID MCloseSAM(void)
{
if (BuiltInDomainHandle)
{
SamCloseHandle(BuiltInDomainHandle);
BuiltInDomainHandle = NULL;
}
if (AccountsDomainHandle)
{
SamCloseHandle(AccountsDomainHandle);
AccountsDomainHandle = NULL;
}
if (LsaHandle)
{
LsaClose(LsaHandle);
LsaHandle = NULL ;
}
if (AccountDomainSid)
{
MFreeMem((CHAR *)AccountDomainSid);
AccountDomainSid = NULL ;
}
NetpFreeWellKnownSids() ;
}
/*
* MSamAddAlias
*
* used to add an alias to the SAM Accounts domain. It is never used
* with builtin domain.
*
* return code: NERR_* or APE_* that can be used for ErrorExit()
* parameters : an ALIAS_ENTRY with both name and comment properly filled.
*/
USHORT MSamAddAlias(ALIAS_ENTRY *pAlias)
{
ULONG RelativeId ;
NTSTATUS NtStatus;
USHORT err = NERR_Success ;
UNICODE_STRING alias_name ;
// setup unicode string for name
if (err = MCreateUnicodeString(pAlias->name, &alias_name))
return(err) ;
// check if its already in BuiltIn domain first
if (MSamCheckIfExists(&alias_name,
BuiltInDomainHandle,
SidTypeAlias))
{
return(NERR_GroupExists) ;
}
// call SAM To do its thing
NtStatus = SamCreateAliasInDomain(AccountsDomainHandle,
&alias_name,
ALIAS_WRITE,
&AliasHandle,
&RelativeId) ;
// did we succeed?
if (!NT_SUCCESS(NtStatus))
{
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
return(err) ;
}
// the call above has setup global handle. MAliasSetInfo will
// use it to set the comment.
return (MAliasSetInfo(pAlias)) ;
}
/*
* MSamDelAlias
*
* used to delete an alias in the SAM Accounts domain. It is never used
* with builtin domain.
*
* return code: NERR_* or APE_* that can be used foe ErrorExit()
* parameters : an ALIAS_ENTRY with name properly filled.
*/
USHORT MSamDelAlias(TCHAR *alias)
{
NTSTATUS NtStatus;
USHORT err = NERR_Success ;
// open the alias to get the handle
if (err = MOpenAlias(alias,WRITE_PRIV,USE_BUILTIN_OR_ACCOUNT))
return(err) ;
// nuke it
NtStatus = SamDeleteAlias( AliasHandle ) ;
if (!NT_SUCCESS(NtStatus))
{
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
}
AliasHandle = NULL ;
return(err) ;
}
#define INIT_ALLOC_COUNT 500
#define PREFERRED_BUFSIZ 16000
/*
* MSamEnumAliases
*
* function to go thru both builtin and accounts domain,
* enum all aliases, build up a table (unsorted) of ALIAS_ENTRY
* structures that is used to display aliases with comments.
*
* return code: NERR_* or APE_*
* parameters: the two OUT parametrs receive a pointer to an array of
* ALIAS_ENTRYs and a count of the number of entries. The
* caller must free pointers within *ppAlias and *ppAlias itself.
*/
USHORT MSamEnumAliases(ALIAS_ENTRY **ppAlias, USHORT2ULONG *pcAlias)
{
ALIAS_ENTRY *pAlias = NULL ;
ULONG cAlias = 0 ;
ULONG cMaxAlias = INIT_ALLOC_COUNT ;
USHORT err ;
/*
* init results to NULL and alloc memory for table
*/
*pcAlias = 0 ;
*ppAlias = NULL ;
if (err = MAllocMem(cMaxAlias * sizeof(ALIAS_ENTRY),
(VOID **)&pAlias))
return err ;
memsetf(pAlias, 0, cMaxAlias*sizeof(ALIAS_ENTRY)) ;
/*
* call the worker routine for BUILT IN domain
*/
err = EnumerateAliases(BuiltInDomainHandle,
&pAlias,
&cAlias,
&cMaxAlias) ;
if (err)
{
MFreeMem((VOID *)pAlias);
return(err) ;
}
/*
* call the worker routine for ACCOUNTS domain
*/
err = EnumerateAliases(AccountsDomainHandle,
&pAlias,
&cAlias,
&cMaxAlias) ;
if (err)
{
MFreeMem((VOID *)pAlias);
return err ;
}
*pcAlias = cAlias ;
*ppAlias = pAlias ;
return(NERR_Success) ;
}
/*
* EnumerateAliases
*
* enumerate the aliases in the desired domain
*
* return code: NERR_* or APE_*
* parameters : DomainHandle is the domain of interest.
* ppAlias points to the start of the buffer to return data in.
* this buffer may already be partially used.
* pcAlias tells us how much of the buffer is alreadt used, so
* we should start from (*ppAlias) + *pcAlias.
* pcMaxAlias tells us how big the total buffer is.
*/
USHORT EnumerateAliases(SAM_HANDLE DomainHandle,
ALIAS_ENTRY **ppAlias,
ULONG *pcAlias,
ULONG *pcMaxAlias)
{
USHORT err ;
NTSTATUS NtStatus ;
PVOID pBuffer = NULL ;
ULONG iEntry ;
ALIAS_ENTRY *pEntry ;
SAM_ENUMERATE_HANDLE hEnum = 0 ;
/*
* setup these guys to point to the right place in the buffer
*/
iEntry = *pcAlias;
pEntry = *ppAlias + iEntry ;
/*
* loop since it is resumable iteration.
*/
do
{
PSAM_RID_ENUMERATION psamRidEnum ;
ULONG count ;
ULONG i ;
/*
* get a buncha aliases
*/
NtStatus = SamEnumerateAliasesInDomain( DomainHandle,
&hEnum,
&pBuffer,
PREFERRED_BUFSIZ,
&count ) ;
if (!NT_SUCCESS(NtStatus))
{
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
pBuffer = NULL ;
goto cleanupandexit ;
}
/*
* extract name of each alias
*/
psamRidEnum = (PSAM_RID_ENUMERATION) pBuffer ;
for (i = 0 ;
i < count ;
i++, psamRidEnum++)
{
USHORT err;
if (iEntry >= *pcMaxAlias)
{
// original buffer not big enough. double and realloc
*pcMaxAlias *= 2 ;
if (err = MReallocMem(*pcMaxAlias * sizeof(ALIAS_ENTRY),
(CHAR **)ppAlias))
goto cleanupandexit ;
}
if (err=MAllocMem(psamRidEnum->Name.Length+sizeof(TCHAR), &pEntry->name))
goto cleanupandexit ;
(void)_tcsncpy(pEntry->name, psamRidEnum->Name.Buffer, psamRidEnum->Name.Length/sizeof(TCHAR));
*(pEntry->name + psamRidEnum->Name.Length/sizeof(TCHAR)) = NULLC;
pEntry->comment = NULL ; // currently not used on Enum
iEntry++ ;
pEntry++ ;
}
// we can now free the buffer
SamFreeMemory((PVOID)pBuffer);
pBuffer = NULL ;
} while (NtStatus == STATUS_MORE_ENTRIES) ;
if (!NT_SUCCESS(NtStatus))
{
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
goto cleanupandexit ;
}
*pcAlias = iEntry ;
return(NERR_Success) ;
cleanupandexit: // only get here on error
if (pBuffer)
SamFreeMemory((PVOID)pBuffer);
MFreeAliasEntries(*ppAlias, iEntry) ;
return(err) ;
}
/*
* MFreeAliasEntries
*
* free up entries in table allocated by MSamEnumerateAliases
*
* return code: none
* parameters: pAlias is pointer to array of entries, cAlias is
* count of entries.
*/
VOID MFreeAliasEntries( ALIAS_ENTRY *pAlias,
ULONG cAlias)
{
while (cAlias--)
{
if (pAlias->name)
{
MFreeMem(pAlias->name) ;
(pAlias++)->name = NULL ;
}
}
}
/*-------------------------- ALIAS operations -------------------------*/
/*
* MOpenAlias
*
* Get a handle to an alias in either ACCOUNT or BUILTIN domain.
*
* return code: NERR_Success if got handle.
* error code that can be used for ErrorExit() otherwise
* parameters: alias is the name of the alias.
* priv is a netcmd defined number that indicates what we
* need to do with the alias.
* domain is one of: USE_BUILTIN_DOMAIN, USE_ACCOUNT_DOMAIN
* or USE_BUILTIN_OR_ACCOUNT.
*/
USHORT MOpenAlias(TCHAR *alias, ULONG priv, ULONG domain)
{
ULONG RelativeId ;
USHORT err = NERR_Success ;
// call worker routine to find the RID
if (err = MSamGetRIDFromName(alias,&RelativeId))
return (err) ;
return MOpenAliasUsingRid( RelativeId, priv, domain );
}
/*-------------------------- ALIAS operations -------------------------*/
/*
* MOpenAliasUsingRid
*
* Get a handle to an alias in either ACCOUNT or BUILTIN domain.
*
* return code: NERR_Success if got handle.
* error code that can be used for ErrorExit() otherwise
* parameters: alias is the name of the alias.
* priv is a netcmd defined number that indicates what we
* need to do with the alias.
* domain is one of: USE_BUILTIN_DOMAIN, USE_ACCOUNT_DOMAIN
* or USE_BUILTIN_OR_ACCOUNT.
*/
USHORT MOpenAliasUsingRid(ULONG RelativeId, ULONG priv, ULONG domain)
{
NTSTATUS NtStatus;
USHORT err = NERR_Success ;
ACCESS_MASK AccessMask ;
/*
* setup right access mask
*/
switch(priv)
{
case READ_PRIV:
AccessMask = ALIAS_READ | ALIAS_EXECUTE ;
break ;
case WRITE_PRIV:
AccessMask = ALIAS_READ | ALIAS_EXECUTE | ALIAS_WRITE | DELETE ;
break ;
default:
AccessMask = 0 ;
return(ERROR_INVALID_PARAMETER) ;
}
/*
* call SAM to open the Alias. We use different domains depending
* on the domain argument.
*/
switch (domain)
{
case USE_BUILTIN_OR_ACCOUNT:
NtStatus = SamOpenAlias(BuiltInDomainHandle,
AccessMask,
RelativeId,
&AliasHandle) ;
if (NtStatus != STATUS_NO_SUCH_ALIAS)
break ;
// otherwise we couldnt find alias, so drop thru to
// to try builtin domain
case USE_ACCOUNT_DOMAIN:
NtStatus = SamOpenAlias(AccountsDomainHandle,
AccessMask,
RelativeId,
&AliasHandle) ;
break ;
case USE_BUILTIN_DOMAIN:
NtStatus = SamOpenAlias(BuiltInDomainHandle,
AccessMask,
RelativeId,
&AliasHandle) ;
break ;
default:
return(NERR_InternalError) ; // this should never happen
}
if (!NT_SUCCESS(NtStatus))
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
return(err) ;
}
/*
* MCloseAlias
*
* Close a handle to an alias.
*
* return code: none
* parameters: none
*/
VOID MCloseAlias(void)
{
if (AliasHandle)
SamCloseHandle(AliasHandle);
AliasHandle = NULL ;
}
/*
* MAliasAddMember
*
* Add a member to an alias that has been opened via MOpenAlias().
*
* return code: NERR_Success if got handle.
* error code that can be used for ErrorExit() otherwise
* parameters: member is the name of the member to add. It may either
* NAME or DOMAIN\NAME.
*/
USHORT MAliasAddMember(TCHAR *member)
{
PSID psid ;
NTSTATUS NtStatus;
USHORT err ;
// translate ascii name to PSID
if (err = MSamGetSIDFromName(member,&psid))
return (err) ;
// add the SID
NtStatus = SamAddMemberToAlias(AliasHandle, psid) ;
// free this memory, since its useless by now
MFreeMem((VOID *)psid) ;
// check for error
if (!NT_SUCCESS(NtStatus))
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
return(err) ;
}
/*
* MDeleteAliasMember
*
* Remove a member from the alias opened by MOpenAlias().
*
* return code: NERR_Success if got handle.
* error code that can be used for ErrorExit() otherwise
* parameters: member is the name of the member to remove. It may be
* NAME or DOMAIN\NAME.
*/
USHORT MAliasDeleteMember(TCHAR *member)
{
PSID psid ;
NTSTATUS NtStatus;
USHORT err = NERR_Success ;
// call worker routine to get SID
if (err = MSamGetSIDFromName(member,&psid))
return (err) ;
// call SAM To do its thing
NtStatus = SamRemoveMemberFromAlias(AliasHandle, psid) ;
// free this memory, since its useless by now
MFreeMem((VOID *)psid) ;
// check for error
if (!NT_SUCCESS(NtStatus))
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
return(err) ;
}
/*
* MAliasEnumMembers
*
* Get the members of an alias.
*
* return code: NERR_Success if got handle.
* error code that can be used for ErrorExit() otherwise
* parameters: members is used to return a pointer to an array of PSZs.
* count is used to return how many members.
*/
USHORT MAliasEnumMembers(TCHAR ***members, USHORT2ULONG *count)
{
PSID *pSids, *next_sid ;
NTSTATUS NtStatus;
USHORT err = NERR_Success ;
TCHAR *pBuffer, **ppchNext, **ppchResults ;
ULONG i, num_read = 0, num_bad = 0 ;
/*
* call SAM to enumerate the members
*/
NtStatus = SamGetMembersInAlias(AliasHandle, &pSids, &num_read) ;
if (!NT_SUCCESS(NtStatus))
{
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
return(err) ;
}
/*
* allocate buffer for array of strings, and NULL it.
*/
if (err = MAllocMem(num_read * sizeof(TCHAR *), &pBuffer) )
{
SamFreeMemory((VOID *)pSids) ;
return(err) ;
}
memsetf(pBuffer, 0, num_read*sizeof(TCHAR *)) ;
ppchNext = ppchResults = (TCHAR **) pBuffer ;
/*
* go thru each SID returned and add the member to out buffer
*/
for (i = 0, next_sid = pSids;
i < num_read;
i++, next_sid++)
{
/*
* convert to strings. On error, free things up.
*/
if (err = MSamGetNameFromSID(*next_sid, ppchNext))
{
if (err == APE_UnknownAccount)
{
// we have bad or deleted SID, just ignore.
// ie. do nothing, dont advance pointer either.
// we do however, keep count of how many we ignore
++num_bad ;
}
else
{
// cleanup & go home
while (i--)
{
MFreeMem(ppchResults[i]) ;
}
SamFreeMemory((TCHAR *)pSids) ;
MFreeMem((VOID *)ppchResults) ;
return(err) ;
}
}
else
++ppchNext ;
}
// dont need this anymore
SamFreeMemory((TCHAR *)pSids) ;
// setup return info
*count = num_read - num_bad ; // number read minus the ones ignored.
*members = ppchResults ;
return(NERR_Success) ;
}
/*
* MAliasFreeMembers
*
* Free the members memory used for members of an alias.
*
* return code: none
* parameters: members is pointer to an array of PSZs.
* count indicates how many members.
*/
VOID MAliasFreeMembers(TCHAR **members, USHORT2ULONG count)
{
while (count--)
{
MFreeMem(*members) ;
++members ;
}
}
/*
* MAliasGetInfo
*
* this wrapper gets info about the alias. currently, only comment is
* returned.
*
* returns: NERR_* or APE_* for ErrorExit()
* parameters: pointer to ALIAS_ENTRY. the comment field of this entry
* will on successful exit point to an ascii string which
* should be MFreeMem()-ed. The name field is untouched.
*/
USHORT MAliasGetInfo(ALIAS_ENTRY *pAlias)
{
NTSTATUS NtStatus;
USHORT err = NERR_Success ;
TCHAR *pBuffer ;
PALIAS_ADM_COMMENT_INFORMATION pAliasCommentInfo ;
TCHAR *pchAdminComment ;
pAlias->comment = NULL ;
NtStatus = SamQueryInformationAlias(AliasHandle,
AliasAdminCommentInformation,
(PVOID *)&pBuffer) ;
if (!NT_SUCCESS(NtStatus))
{
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
return(err) ;
}
pAliasCommentInfo = (PALIAS_ADM_COMMENT_INFORMATION)pBuffer ;
if (err = MAllocMem(
pAliasCommentInfo->AdminComment.Length+sizeof(TCHAR),
&pchAdminComment))
return(err) ;
_tcsncpy(pchAdminComment, pAliasCommentInfo->AdminComment.Buffer,
pAliasCommentInfo->AdminComment.Length/sizeof(TCHAR));
*(pchAdminComment+pAliasCommentInfo->AdminComment.Length/sizeof(TCHAR))
= NULLC;
SamFreeMemory((PVOID)pBuffer) ;
pAlias->comment = pchAdminComment ;
return(NERR_Success) ;
}
/*
* Set Alias information. Currently, only comment is settable.
*
* return code: NERR_* or APE_* that can be used for ErrorExit()
*
* parameters: alias is pointer to ALIAS_ENTRY that supplies
* the comment. If comment is NULL, do nothing. "" will
* clear the comment.
*/
USHORT MAliasSetInfo(ALIAS_ENTRY *pAlias)
{
NTSTATUS NtStatus;
USHORT cBuffer, err = NERR_Success ;
PALIAS_ADM_COMMENT_INFORMATION pComment ;
TCHAR *pBuffer ;
// this is all we set currently. if nothing, just return.
if (pAlias->comment == NULL)
return(NERR_Success) ;
// allocate the buffer
cBuffer = sizeof(ALIAS_ADM_COMMENT_INFORMATION) ;
if (err = MAllocMem(cBuffer, &pBuffer))
return(err) ;
pComment = (PALIAS_ADM_COMMENT_INFORMATION) pBuffer ;
// set it up with the comment
if (err = MCreateUnicodeString(pAlias->comment, &(pComment->AdminComment)))
{
// free up previously alloc-ed buffer and exit
MFreeMem(pBuffer) ;
return(err) ;
}
// call SAM to do its thing
NtStatus = SamSetInformationAlias(AliasHandle,
AliasAdminCommentInformation,
pBuffer) ;
// free up the buffer we alloc-ed
MFreeMem(pBuffer) ;
// map errors if any
if (!NT_SUCCESS(NtStatus))
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
return(err) ;
}
/*----------------------- worker routines ------------------------------*/
/*
* MSamGetSIDFromRID
*
* From a domain sid and a rid, create the PSID.
* Caller must free the PSID with MFreeMem().
*
* return code: NERR_* or APE_*
* parameters: pSidDomain is the domain sid.
* rid is the RID.
* ppSID is used to return the PSID which should be
* freed using MFreeMem().
*/
USHORT MSamGetSIDFromRID(PSID pSidDomain,
ULONG rid,
PSID *ppSID)
{
USHORT err ;
ULONG cbSid ;
DWORD *pdwLastSubAuthority ;
UCHAR *pcSubAuthority ;
// allocate mem for it. we need room for one extra Sub Authority.
cbSid = GetSidLengthRequired((UCHAR)
((*GetSidSubAuthorityCount(pSidDomain))+1)) ;
if (err = MAllocMem(cbSid, (CHAR **)ppSID ))
return err ;
// make copy so we can mess with it
if (!CopySid(cbSid, *ppSID, pSidDomain))
{
MFreeMem(*ppSID) ;
*ppSID = NULL ;
return NERR_InternalError ;
}
// get the last subauthority and set it with RelativeID,
// thereby generating the account SID we wanted in first place
pcSubAuthority = GetSidSubAuthorityCount((PSID)*ppSID) ;
(*pcSubAuthority)++ ;
pdwLastSubAuthority = GetSidSubAuthority((PSID)*ppSID,
*pcSubAuthority-1) ;
*pdwLastSubAuthority = rid ;
return NERR_Success ;
}
/*
* MSamGetSIDFromName
*
* From a name, lookup the PSID.
* Caller must free the PSID with MFreeMem().
*
* return code: NERR_* or APE_*
* parameters: name is account name. May be domain\user format.
* ppSID is used to return the PSID which should be
* freed using MFreeMem().
*/
USHORT MSamGetSIDFromName(TCHAR *name,
PSID *ppSID)
{
NTSTATUS NtStatus;
USHORT err = NERR_Success ;
PLSA_REFERENCED_DOMAIN_LIST pRefDomains ;
PLSA_TRANSLATED_SID pTranslatedSids ;
UNICODE_STRING UnicodeStr ;
ULONG cbSid ;
DWORD *pdwLastSubAuthority ;
BYTE *pSidBuffer ;
PSID pDomainSid ;
UCHAR *pcSubAuthority ;
LONG iDomain ;
*ppSID = NULL ;
// create the unicode structure for LSA
if (err = MCreateUnicodeString(name, &UnicodeStr))
return (err) ;
// do lookup
NtStatus = LsaLookupNames(LsaHandle,
1,
&UnicodeStr,
&pRefDomains,
&pTranslatedSids) ;
if (!NT_SUCCESS(NtStatus))
{
if (NtStatus == STATUS_NONE_MAPPED)
err = APE_UnknownAccount ;
else
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
return(err) ;
}
// get to the right domain
iDomain = pTranslatedSids->DomainIndex ;
if (iDomain < 0)
return(APE_UnknownAccount) ;
pDomainSid = ((pRefDomains->Domains)+iDomain)->Sid ;
// allocate mem for it. we need room for one extra Sub Authority.
cbSid = GetSidLengthRequired((UCHAR)
((*GetSidSubAuthorityCount(pDomainSid))+1)) ;
if (err = MAllocMem(cbSid, &pSidBuffer ))
goto exitpoint ;
// make copy so we can mess with it
if (!CopySid(cbSid, pSidBuffer, pDomainSid))
{
err = NERR_InternalError ; // shouldnt happen
goto exitpoint ;
}
// get the last subauthority and set it with RelativeID,
// thereby generating the account SID we wanted in first place
pcSubAuthority = GetSidSubAuthorityCount((PSID)pSidBuffer) ;
(*pcSubAuthority)++ ;
pdwLastSubAuthority = GetSidSubAuthority((PSID)pSidBuffer,
*pcSubAuthority-1) ;
*pdwLastSubAuthority = pTranslatedSids->RelativeId ;
*ppSID = (PSID) pSidBuffer ;
err = NERR_Success ;
exitpoint:
LsaFreeMemory(pTranslatedSids) ;
LsaFreeMemory(pRefDomains) ;
return(err) ;
}
/*
* MSamGetRIDFromName
*
* From a name, lookup the RID
*
* return code: NERR_* or APE_*
* parameters: name is account name. May be domain\user/
* pRID is used to return the RID.
*/
USHORT MSamGetRIDFromName(TCHAR *name,
ULONG *pRID)
{
NTSTATUS NtStatus;
USHORT err = NERR_Success ;
PLSA_REFERENCED_DOMAIN_LIST Domains ;
PLSA_TRANSLATED_SID Sids ;
UNICODE_STRING UnicodeStr ;
PSID_NAME_USE pSidNameUse = NULL;
PULONG pRidList = NULL ;
// create the unicode structure for LSA
if (err = MCreateUnicodeString(name, &UnicodeStr))
return (err) ;
// do lookup in local accounts first in case same as machine name
// and LSA will return the wrong one.
NtStatus = SamLookupNamesInDomain( AccountsDomainHandle,
1,
&UnicodeStr,
&pRidList,
&pSidNameUse );
// if succeed, take a close look
if (NT_SUCCESS(NtStatus))
{
// what type of name is this?
switch (*pSidNameUse)
{
case SidTypeAlias :
// found what we wanted
*pRID = *pRidList ;
SamFreeMemory(pRidList) ;
SamFreeMemory(pSidNameUse) ;
return NERR_Success ;
case SidTypeWellKnownGroup :
case SidTypeGroup:
case SidTypeUser :
case SidTypeDomain :
case SidTypeDeletedAccount :
case SidTypeInvalid :
case SidTypeUnknown :
default:
// carry on by looking up via LSA
SamFreeMemory(pRidList) ;
SamFreeMemory(pSidNameUse) ;
break ;
}
}
NtStatus = LsaLookupNames(LsaHandle,
1,
&UnicodeStr,
&Domains,
&Sids) ;
if (!NT_SUCCESS(NtStatus))
{
if (NtStatus == STATUS_NONE_MAPPED)
err = APE_UnknownAccount ;
else
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
return(err) ;
}
*pRID = Sids->RelativeId ;
LsaFreeMemory(Sids) ;
LsaFreeMemory(Domains) ;
return(NERR_Success) ;
}
/*
* MSamGetNameFromRid
*
* From a RID, lookup the ASCII name.
*
* return code: NERR_* or APE_*
* parameters: psid is the sid to lookup
* name is used to return the pointer
* to ascii name that should be MFreeMem()-ed.
* fIsBuiltin indicates to use the Builtin Domain.
*/
USHORT MSamGetNameFromRid(ULONG RelativeId,
TCHAR **name,
BOOL fIsBuiltin )
{
NTSTATUS NtStatus;
USHORT err = NERR_Success;
PUNICODE_STRING pUniString;
PSID_NAME_USE pSidNameUse;
ULONG cbNameLen ;
TCHAR *pchName ;
NtStatus = SamLookupIdsInDomain( fIsBuiltin ? BuiltInDomainHandle
: AccountsDomainHandle,
1,
&RelativeId,
&pUniString,
&pSidNameUse );
if (!NT_SUCCESS(NtStatus))
{
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
// if not found, map to unknown account
if (err == NERR_GroupNotFound || err == NERR_UserNotFound)
return(APE_UnknownAccount) ;
return(err) ;
}
// what type of name is this?
switch (*pSidNameUse)
{
case SidTypeUser :
case SidTypeGroup:
case SidTypeAlias :
case SidTypeWellKnownGroup :
// this is OK case
break ;
case SidTypeDomain :
// the above shouldnt happen. we only deal with users/groups.
// if it does, behave as if cannot find.
case SidTypeDeletedAccount :
case SidTypeInvalid :
case SidTypeUnknown :
default:
err = APE_UnknownAccount ;
goto exitpoint ;
}
// alloc mem for name. +1 for terminator
cbNameLen = (pUniString->Length+1)*sizeof(WCHAR) ;
if (err = MAllocMem(cbNameLen, &pchName))
goto exitpoint ;
// init the buffer to zeros, then build the WCHAR name
memsetf(pchName, 0, cbNameLen) ;
wcsncpy((LPWSTR)pchName,
pUniString->Buffer,
cbNameLen/sizeof(WCHAR) - 1) ;
*name = pchName ;
err = NERR_Success ;
exitpoint:
SamFreeMemory(pUniString) ;
SamFreeMemory(pSidNameUse) ;
return(err) ;
}
/*
* MSamGetNameFromSID
*
* From a SID, lookup the ASCII name.
*
* return code: NERR_* or APE_*
* parameters: psid is the sid to lookup
* name is used to return the pointer
* to ascii name that should be MFreeMem()-ed.
*/
USHORT MSamGetNameFromSID(PSID psid,
TCHAR **name)
{
NTSTATUS NtStatus;
USHORT err = NERR_Success ;
PLSA_REFERENCED_DOMAIN_LIST pRefDomains ;
PLSA_TRANSLATED_NAME pTranslatedNames ;
ULONG cbNameLen, cbDomainLen, cbTotal ;
TCHAR *pchName ;
LONG iDomain ;
// call LSA to lookup the SID
NtStatus = LsaLookupSids(LsaHandle,
1,
&psid,
&pRefDomains,
&pTranslatedNames) ;
if (!NT_SUCCESS(NtStatus))
{
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
// if not found, map to unknown account
if (err == NERR_GroupNotFound || err == NERR_UserNotFound)
return(APE_UnknownAccount) ;
return(err) ;
}
// what type of name is this?
switch (pTranslatedNames->Use)
{
case SidTypeUser :
case SidTypeGroup:
case SidTypeAlias :
case SidTypeWellKnownGroup :
// this is OK case
break ;
case SidTypeDomain :
// the above shouldnt happen. we only deal with users/groups.
// if it does, behave as if cannot find.
case SidTypeDeletedAccount :
case SidTypeInvalid :
case SidTypeUnknown :
default:
return(APE_UnknownAccount) ;
}
// get to right domain
iDomain = pTranslatedNames->DomainIndex ;
if (iDomain < 0)
return(APE_UnknownAccount) ;
// alloc mem for name. +1 for '\', and another +1 for terminator
cbNameLen = pTranslatedNames->Name.Length ;
cbDomainLen = ((pRefDomains->Domains)+iDomain)->Name.Length ;
cbTotal = (cbNameLen+cbDomainLen+1+1)*sizeof(WCHAR) ;
if (err = MAllocMem(cbTotal, &pchName))
goto exitpoint ;
// init the buffer to zeros, then build the WCHAR name by concatenating
// the domain name with the user name. but dont do it if its the account
// domain or builtin (in this case, only show username).
memsetf(pchName, 0, cbTotal) ;
if (!EqualSid( ((pRefDomains->Domains)+iDomain)->Sid,
AccountDomainSid ) &&
!EqualSid( ((pRefDomains->Domains)+iDomain)->Sid,
BuiltinDomainSid ))
{
wcsncpy((LPWSTR)pchName,
((pRefDomains->Domains)+iDomain)->Name.Buffer,
cbDomainLen/sizeof(WCHAR)) ;
wcscat((LPWSTR)pchName, L"\\") ;
}
wcsncat((LPWSTR)pchName,
pTranslatedNames->Name.Buffer,
cbNameLen/sizeof(WCHAR)) ;
*name = pchName ;
err = NERR_Success ;
exitpoint:
LsaFreeMemory(pTranslatedNames) ;
LsaFreeMemory(pRefDomains) ;
return(err) ;
}
/*
* MCreateUnicodeString
*
* build a UNICODE_STRING from an string.
*/
USHORT MCreateUnicodeString(TCHAR *pch, PUNICODE_STRING pUnicodeStr)
{
pUnicodeStr->Length = (USHORT)(_tcslen(pch) * sizeof(WCHAR)) ;
pUnicodeStr->Buffer = pch ;
pUnicodeStr->MaximumLength = pUnicodeStr->Length + (USHORT)sizeof(WCHAR) ;
return(NERR_Success) ;
}
/*---------------------- alias memberships of a user ----------------------*/
/*
* MUserEnumAliases
*
* Get the members of an alias.
*
* return code: NERR_Success if got handle.
* error code that can be used for ErrorExit() otherwise
* parameters: members is used to return a pointer to an array of PSZs.
* count is used to return how many members.
*/
USHORT MUserEnumAliases(TCHAR *user, TCHAR ***members, USHORT2ULONG *count)
{
PSID pSidUser = NULL ;
ULONG *pRidAccountAliases = NULL ;
ULONG *pRidBuiltinAliases = NULL ;
ULONG *next_rid ;
NTSTATUS NtStatus;
USHORT err = NERR_Success ;
TCHAR *pBuffer, **ppchNext, **ppchResults ;
ULONG i, cAccountAliases = 0, cBuiltinAliases = 0 ;
/*
* initialize the return info
*/
*members = NULL,
*count = 0 ;
/*
* get sid from the user name
*/
if (err = MSamGetSIDFromName(user, &pSidUser))
return(err) ;
/*
* call SAM to enumerate the aliases the user is in for account domain
*/
NtStatus = SamGetAliasMembership(AccountsDomainHandle,
1,
&pSidUser,
&cAccountAliases,
&pRidAccountAliases) ;
if (!NT_SUCCESS(NtStatus))
{
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
goto exitpoint ;
}
/*
* call SAM to enumerate the aliases the user is in for builtin domain
*/
NtStatus = SamGetAliasMembership(BuiltInDomainHandle,
1,
&pSidUser,
&cBuiltinAliases,
&pRidBuiltinAliases) ;
if (!NT_SUCCESS(NtStatus))
{
err = (USHORT)NetpNtStatusToApiStatus(NtStatus) ;
goto exitpoint ;
}
/*
* if none, return now
*/
if ((cBuiltinAliases + cAccountAliases) == 0)
{
err = NERR_Success ;
goto exitpoint ;
}
/*
* allocate buffer for array of strings, and NULL it.
*/
if (err = MAllocMem( (cAccountAliases+cBuiltinAliases) * sizeof(TCHAR *),
&pBuffer) )
goto exitpoint ;
memsetf(pBuffer, 0, (cAccountAliases+cBuiltinAliases)*sizeof(TCHAR *)) ;
ppchNext = ppchResults = (TCHAR **) pBuffer ;
/*
* go thru each account alias returned and add the member to out buffer
*/
for (i = 0, next_rid = pRidAccountAliases;
i < cAccountAliases;
i++, next_rid++)
{
BYTE *pSidBuffer ;
/*
* first off, convert the RID to SID. what a drag...
*/
if (err = MSamGetSIDFromRID(AccountDomainSid, *next_rid,
(PSID *)&pSidBuffer))
goto exitpoint ;
/*
* convert to strings. On error, free things up.
*/
if (err = MSamGetNameFromSID(pSidBuffer, ppchNext))
{
if (err == APE_UnknownAccount)
{
// we have bad or deleted SID, just ignore.
// ie. do nothing, dont advance pointer either.
}
else
{
// cleanup & go home
while (i--)
MFreeMem(ppchResults[i]) ;
MFreeMem((VOID *)ppchResults) ;
goto exitpoint ;
}
}
else
++ppchNext ;
MFreeMem(pSidBuffer) ;
}
/*
* go thru each builtin alias returned and add the member to out buffer
*/
for (next_rid = pRidBuiltinAliases;
i < cBuiltinAliases + cAccountAliases;
i++, next_rid++)
{
BYTE *pSidBuffer ;
/*
* first off, convert the RID to SID. what a drag...
*/
if (err = MSamGetSIDFromRID(BuiltinDomainSid, *next_rid,
(PSID *)&pSidBuffer))
goto exitpoint ;
/*
* convert to strings. On error, free things up.
*/
if (err = MSamGetNameFromSID(pSidBuffer, ppchNext))
{
if (err == APE_UnknownAccount)
{
// we have bad or deleted SID, just ignore.
// ie. do nothing, dont advance pointer either.
}
else
{
// cleanup & go home
while (i--)
MFreeMem(ppchResults[i]) ;
MFreeMem((VOID *)ppchResults) ;
goto exitpoint ;
}
}
else
++ppchNext ;
MFreeMem(pSidBuffer) ;
}
// setup return info
*count = i ; // number read minus the ones ignored.
*members = ppchResults ;
err = NERR_Success ;
exitpoint:
// dont need these anymore
if (pRidBuiltinAliases) SamFreeMemory((CHAR *)pRidBuiltinAliases) ;
if (pRidAccountAliases) SamFreeMemory((CHAR *)pRidAccountAliases) ;
if (pSidUser) MFreeMem(pSidUser) ;
return(err) ;
}
/*
* MUserFreeAliases
*
* Free the members memory used for members of an alias.
*
* return code: none
* parameters: members is pointer to an array of PSZs.
* count indicates how many members.
*/
VOID MUserFreeAliases(TCHAR **members, USHORT2ULONG count)
{
while (count--)
{
MFreeMem(*members) ;
++members ;
}
}
/*---------------------- misc sam/lsa related routines ----------------------*/
/*
* MSamCheckIfExists
*
* From an alias name, check if its already in the builtin Domain.
*
* return value: TRUE if it is, FALSE otherwise.
* parameters: name is account name. May be domain\user format.
*/
BOOL MSamCheckIfExists(PUNICODE_STRING pAccount,
SAM_HANDLE hDomain,
SID_NAME_USE use)
{
NTSTATUS NtStatus;
PSID_NAME_USE pSidNameUse ;
PULONG pulRelativeIds ;
// do lookup
NtStatus = SamLookupNamesInDomain(hDomain,
1,
pAccount,
&pulRelativeIds,
&pSidNameUse) ;
if (!NT_SUCCESS(NtStatus))
return(FALSE) ;
// could not translate, assume not there
if ( (*pulRelativeIds != 0) && (*pSidNameUse == use) )
{
//
// if the RID is non zero (successful lookup and
// the name use is the same as the queried one,
// assume we have match.
//
SamFreeMemory(pSidNameUse) ;
SamFreeMemory(pulRelativeIds) ;
return TRUE ;
}
SamFreeMemory(pSidNameUse) ;
SamFreeMemory(pulRelativeIds) ;
return FALSE ;
}