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.
 
 
 
 
 
 

1472 lines
36 KiB

/**********************************************************************/
/** Microsoft Windows NT **/
/** Copyright(c) Microsoft Corp., 1993 **/
/**********************************************************************/
/*
security.c
This module manages security for the FTPD Service.
FILE HISTORY:
KeithMo 07-Mar-1993 Created.
*/
#include "ftpdp.h"
#pragma hdrstop
BEGIN_EXTERN_C
#include <ntsam.h>
#include <ntlsa.h>
#include <ntmsv1_0.h>
#include <crypt.h>
#include <logonmsv.h>
END_EXTERN_C
//
// Private constants.
//
#define TOKEN_SOURCE_NAME FTPD_SERVICE_NAME
#define LOGON_PROCESS_NAME "FTP Server"
#define LOGON_ORIGIN "FTP Server"
#define SUBSYSTEM_NAME FTPD_SERVICE_NAME_W
#define OBJECT_NAME FTPD_SERVICE_NAME_W
#define OBJECTTYPE_NAME FTPD_SERVICE_NAME_W
//
// Private globals.
//
TOKEN_SOURCE tokenSource = { TOKEN_SOURCE_NAME, {0, 0} };
HANDLE hLsaAuthenticator;
ULONG idAuthenticator;
LSA_OPERATIONAL_MODE SecurityMode;
LSA_HANDLE hPasswordSecret;
//
// Well-known SIDs.
//
PSID psidWorld;
PSID psidLocalSystem;
PSID psidAdmins;
PSID psidServerOps;
PSID psidPowerUsers;
//
// The API security object. Client access to the FTP APIs
// are validated against this object.
//
PSECURITY_DESCRIPTOR sdApiObject;
//
// This table maps generic rights (like GENERIC_READ) to
// specific rights (like FTPD_QUERY_SECURITY).
//
GENERIC_MAPPING FtpApiObjectMapping =
{
FTPD_GENERIC_READ, // generic read
FTPD_GENERIC_WRITE, // generic write
FTPD_GENERIC_EXECUTE, // generic execute
FTPD_ALL_ACCESS // generic all
};
#if DBG
CHAR * apszAccessTypes[] = { "read",
"write",
"create",
"delete" };
#endif // DBG
//
// Private prototypes.
//
NTSTATUS
OpenPasswordSecret(
LSA_HANDLE * phSecret
);
NTSTATUS
CreateWellKnownSids(
VOID
);
VOID
FreeWellKnownSids(
VOID
);
NTSTATUS
CreateApiSecurityObject(
VOID
);
VOID
DeleteApiSecurityObject(
VOID
);
//
// Public functions.
//
/*******************************************************************
NAME: InitializeSecurity
SYNOPSIS: Initializes security authentication & impersonation
routines.
RETURNS: APIERR - NO_ERROR if successful, otherwise a Win32
error code.
NOTES: This routine may only be called by a single thread
of execution; it is not necessarily multi-thread safe.
HISTORY:
KeithMo 07-Mar-1993 Created.
********************************************************************/
APIERR
InitializeSecurity(
VOID
)
{
NTSTATUS ntStatus;
STRING LogonProcessName;
STRING PackageName;
IF_DEBUG( SECURITY )
{
FTPD_PRINT(( "initializing security\n" ));
}
//
// Create well-known SIDs.
//
ntStatus = CreateWellKnownSids();
//
// Create the API security object.
//
if( NT_SUCCESS(ntStatus) )
{
ntStatus = CreateApiSecurityObject();
}
//
// Open a handle to the LSA Secret Object containing
// the password for Anonymous user logon.
//
if( NT_SUCCESS(ntStatus) )
{
ntStatus = OpenPasswordSecret( &hPasswordSecret );
if( !NT_SUCCESS(ntStatus) )
{
IF_DEBUG( SECURITY )
{
FTPD_PRINT(( "cannot open password secret, error %08lX\n",
ntStatus ));
if( fAllowAnonymous )
{
FTPD_PRINT(( "anonymous logon disabled\n" ));
}
}
fAllowAnonymous = FALSE;
ntStatus = STATUS_SUCCESS;
}
}
//
// Register as an LSA logon process.
//
if( NT_SUCCESS(ntStatus) )
{
RtlInitString( &LogonProcessName, LOGON_PROCESS_NAME );
ntStatus = LsaRegisterLogonProcess( &LogonProcessName,
&hLsaAuthenticator,
&SecurityMode );
if( !NT_SUCCESS(ntStatus) )
{
FTPD_PRINT(( "cannot register as logon process, error %08lX\n",
ntStatus ));
}
}
//
// Find the proper authentication package.
//
if( NT_SUCCESS(ntStatus) )
{
RtlInitString( &PackageName, MSV1_0_PACKAGE_NAME );
ntStatus = LsaLookupAuthenticationPackage( hLsaAuthenticator,
&PackageName,
&idAuthenticator );
if( !NT_SUCCESS(ntStatus) )
{
FTPD_PRINT(( "cannot lookup authentication package, error %08lX\n",
ntStatus ));
}
}
if( !NT_SUCCESS(ntStatus) )
{
APIERR err = (APIERR)RtlNtStatusToDosError( ntStatus );
FtpdLogEvent( FTPD_EVENT_CANNOT_INITIALIZE_SECURITY,
0,
NULL,
err );
FTPD_PRINT(( "cannot initialize security, error %08lX (mapped to %lu)\n",
ntStatus,
err ));
return err;
}
//
// If licensing is enabled, set the LSA_CALL_LICENSE_SERVER flag
// in the idAuthenticator value.
//
if( fEnableLicensing )
{
IF_DEBUG( SECURITY )
{
FTPD_PRINT(( "InitializeSecurity: setting LSA_CALL_LICENSE_SERVER flag\n" ));
}
idAuthenticator |= LSA_CALL_LICENSE_SERVER;
}
//
// Success!
//
IF_DEBUG( SECURITY )
{
FTPD_PRINT(( "security initialized\n" ));
}
return NO_ERROR;
} // InitializeSecurity
/*******************************************************************
NAME: TerminateSecurity
SYNOPSIS: Terminate security authentication & impersonation
routines.
NOTES: This routine may only be called by a single thread
of execution; it is not necessarily multi-thread safe.
HISTORY:
KeithMo 07-Mar-1993 Created.
********************************************************************/
VOID
TerminateSecurity(
VOID
)
{
IF_DEBUG( SECURITY )
{
FTPD_PRINT(( "terminating security\n" ));
}
if( hAnonymousToken != NULL )
{
DeleteUserToken( hAnonymousToken );
hAnonymousToken = NULL;
}
if( hLsaAuthenticator != NULL )
{
LsaDeregisterLogonProcess( hLsaAuthenticator );
hLsaAuthenticator = NULL;
idAuthenticator = 0;
SecurityMode = 0;
}
if( hPasswordSecret != NULL )
{
LsaClose( hPasswordSecret );
hPasswordSecret = NULL;
}
FreeWellKnownSids();
DeleteApiSecurityObject();
IF_DEBUG( SECURITY )
{
FTPD_PRINT(( "security terminated\n" ));
}
} // TerminateSecurity
/*******************************************************************
NAME: ValidateUser
SYNOPSIS: Validate a given domain/user/password tuple.
ENTRY: pszDomainName - The user's domain (NULL = current).
pszUserName - The user's name.
pszPassword - The user's (plaintext) password.
pfAsGuest - Will receive TRUE if the user was validated
with guest privileges.
pfLicenseExceeded - Will receive TRUE if the logon
was denied due to license restrictions.
RETURNS: HANDLE - An impersonation token, NULL if user cannot
be validated.
HISTORY:
KeithMo 07-Mar-1993 Created.
********************************************************************/
HANDLE
ValidateUser(
CHAR * pszDomainName,
CHAR * pszUserName,
CHAR * pszPassword,
BOOL * pfAsGuest,
BOOL * pfLicenseExceeded
)
{
NTSTATUS ntStatus;
NTSTATUS ntSubStatus;
STRING OriginName;
QUOTA_LIMITS Quotas;
LUID LogonId;
HANDLE hToken;
PMSV1_0_LM20_LOGON_PROFILE pProfile;
ULONG cbProfile;
PMSV1_0_LM20_LOGON pInfo;
ULONG cbInfo;
CHAR * pszNextChar;
UNICODE_STRING DomainName;
UNICODE_STRING UserName;
UNICODE_STRING Workstation;
STRING AsciiPassword;
UNICODE_STRING UnicodePassword;
IF_DEBUG( SECURITY )
{
FTPD_PRINT(( "validating user %s\\%s\n",
( pszDomainName == NULL ) ? "." : pszDomainName,
pszUserName ));
}
//
// Put our buffers & token into known states.
//
pInfo = NULL;
pProfile = NULL;
hToken = NULL;
//
// Initialize a number of strings.
//
RtlInitString( &OriginName, LOGON_ORIGIN );
RtlInitString( &AsciiPassword, pszPassword );
RtlInitUnicodeString( &DomainName, NULL );
RtlInitUnicodeString( &UserName, NULL );
RtlInitUnicodeString( &UnicodePassword, NULL );
RtlInitUnicodeString( &Workstation, L"" );
if( !RtlCreateUnicodeStringFromAsciiz( &DomainName, pszDomainName ) ||
!RtlCreateUnicodeStringFromAsciiz( &UserName, pszUserName ) ||
!RtlCreateUnicodeStringFromAsciiz( &UnicodePassword, pszPassword ) )
{
ntStatus = STATUS_NO_MEMORY;
goto Cleanup;
}
//
// The ASCII password is for downlevel compatability.
// It must always be uppercase.
//
RtlUpperString( &AsciiPassword, &AsciiPassword );
//
// Calculate the size of the authentication buffer. Note
// that all of the various strings must be within this
// single info buffer.
//
cbInfo = sizeof(MSV1_0_LM20_LOGON)
+ DomainName.MaximumLength + 1
+ UserName.MaximumLength + 1
+ Workstation.MaximumLength + 1
+ UnicodePassword.MaximumLength + 1
+ AsciiPassword.MaximumLength;
//
// Allocate the info buffer.
//
pInfo = (PMSV1_0_LM20_LOGON)FTPD_ALLOC( cbInfo );
if( pInfo == NULL )
{
ntStatus = STATUS_NO_MEMORY;
goto Cleanup;
}
//
// Initialize the info buffer, packing the various
// strings into the buffer.
//
pszNextChar = (CHAR *)pInfo + sizeof(MSV1_0_LM20_LOGON);
pInfo->MessageType = MsV1_0Lm20Logon;
wcscpy( (WCHAR *)pszNextChar, DomainName.Buffer );
pInfo->LogonDomainName.Length = DomainName.Length;
pInfo->LogonDomainName.MaximumLength = DomainName.MaximumLength;
pInfo->LogonDomainName.Buffer = (WCHAR *)pszNextChar;
pszNextChar += DomainName.MaximumLength;
wcscpy( (WCHAR *)pszNextChar, UserName.Buffer );
pInfo->UserName.Length = UserName.Length;
pInfo->UserName.MaximumLength = UserName.MaximumLength;
pInfo->UserName.Buffer = (WCHAR *)pszNextChar;
pszNextChar += UserName.MaximumLength;
wcscpy( (WCHAR *)pszNextChar, Workstation.Buffer );
pInfo->Workstation.Length = Workstation.Length;
pInfo->Workstation.MaximumLength = Workstation.MaximumLength;
pInfo->Workstation.Buffer = (WCHAR *)pszNextChar;
pszNextChar += Workstation.MaximumLength;
RtlZeroMemory( &pInfo->ChallengeToClient, MSV1_0_CHALLENGE_LENGTH );
wcscpy( (WCHAR *)pszNextChar, UnicodePassword.Buffer );
pInfo->CaseSensitiveChallengeResponse.Length = UnicodePassword.Length;
pInfo->CaseSensitiveChallengeResponse.MaximumLength = UnicodePassword.MaximumLength;
pInfo->CaseSensitiveChallengeResponse.Buffer = (PCHAR)pszNextChar;
pszNextChar += UnicodePassword.MaximumLength;
strcpy( pszNextChar, AsciiPassword.Buffer );
pInfo->CaseInsensitiveChallengeResponse.Length = AsciiPassword.Length;
pInfo->CaseInsensitiveChallengeResponse.MaximumLength = AsciiPassword.MaximumLength;
pInfo->CaseInsensitiveChallengeResponse.Buffer = pszNextChar;
pszNextChar += AsciiPassword.MaximumLength;
pInfo->ParameterControl = CLEARTEXT_PASSWORD_ALLOWED;
//
// Try to create the impersonation token.
//
ntStatus = LsaLogonUser( hLsaAuthenticator,
&OriginName,
Network,
idAuthenticator,
pInfo,
cbInfo,
NULL,
&tokenSource,
(PVOID *)&pProfile,
&cbProfile,
&LogonId,
&hToken,
&Quotas,
&ntSubStatus );
RtlZeroMemory( (PVOID)UnicodePassword.Buffer, UnicodePassword.Length );
RtlZeroMemory( (PVOID)AsciiPassword.Buffer, AsciiPassword.Length );
*pfLicenseExceeded = ( ntStatus == STATUS_LICENSE_QUOTA_EXCEEDED );
if( !NT_SUCCESS(ntStatus) )
{
//
// Ensure hToken is NULLed on error.
//
hToken = NULL;
//
// MSV 1.0 uses STATUS_ACCOUNT_RESTRICTION to indicate SubStatus.
//
if( ntStatus == STATUS_ACCOUNT_RESTRICTION )
{
ntStatus = ntSubStatus;
}
goto Cleanup;
}
*pfAsGuest = !!( pProfile->UserFlags & LOGON_GUEST );
Cleanup:
if( pProfile != NULL )
{
LsaFreeReturnBuffer( (PVOID)pProfile );
}
if( pInfo != NULL )
{
FTPD_FREE( pInfo );
}
if( DomainName.Buffer != NULL )
{
RtlFreeUnicodeString( &DomainName );
}
if( UserName.Buffer != NULL )
{
RtlFreeUnicodeString( &UserName );
}
if( UnicodePassword.Buffer != NULL )
{
RtlFreeUnicodeString( &UnicodePassword );
}
IF_DEBUG( SECURITY )
{
if( NT_SUCCESS(ntStatus) )
{
FTPD_PRINT(( "%s\\%s validated, token = %08lX\n",
( pszDomainName == NULL ) ? "." : pszDomainName,
pszUserName,
hToken ));
}
else
{
FTPD_PRINT(( "could not validate %s\\%s, error %08lX\n",
( pszDomainName == NULL ) ? "." : pszDomainName,
pszUserName,
ntStatus ));
}
}
return hToken;
} // ValidateUser
/*******************************************************************
NAME: ImpersonateUser
SYNOPSIS: Causes the current thread to impersonate the user
represented by the given impersonation token.
ENTRY: hToken - A handle to an impersonation token created
with ValidateUser. NULL can be specified to
"revert to self".
RETURNS: BOOL - TRUE if successful, FALSE otherwise.
NOTE: If this function is successful and a non-NULL
impersonation token was specified, then the current
thread IS the specified user.
HISTORY:
KeithMo 07-Mar-1993 Created.
********************************************************************/
BOOL
ImpersonateUser(
HANDLE hToken
)
{
NTSTATUS ntStatus;
IF_DEBUG( SECURITY )
{
FTPD_PRINT(( "impersonating user token %08lX\n",
hToken ));
}
ntStatus = NtSetInformationThread( NtCurrentThread(),
ThreadImpersonationToken,
&hToken,
sizeof(hToken) );
if( !NT_SUCCESS(ntStatus) )
{
FTPD_PRINT(( "cannot impersonate user token %08lX, error %08lX\n",
hToken,
ntStatus ));
}
return NT_SUCCESS(ntStatus);
} // ImpersonateUser
/*******************************************************************
NAME: DeleteUserToken
SYNOPSIS: Deletes a token created with ValidateUser.
ENTRY: hToken - An impersonation token created with
ValidateUser.
RETURNS: BOOL - TRUE if successful, FALSE otherwise.
HISTORY:
KeithMo 07-Mar-1993 Created.
********************************************************************/
BOOL
DeleteUserToken(
HANDLE hToken
)
{
NTSTATUS ntStatus;
IF_DEBUG( SECURITY )
{
FTPD_PRINT(( "deleting user token %08lX\n",
hToken ));
}
ntStatus = NtClose( hToken );
if( !NT_SUCCESS(ntStatus) )
{
FTPD_PRINT(( "cannot delete impersonation token %08lX, error %08lX\n",
hToken,
ntStatus ));
}
return NT_SUCCESS(ntStatus);
} // DeleteUserToken
/*******************************************************************
NAME: GetAnonymousPassword
SYNOPSIS: Retrieves the password for Anonymous logon.
ENTRY: pszPassword - Will receive the password. This buffer
must be at least PWLEN+1 characters in length.
RETURNS: BOOL - TRUE if password retrieved, FALSE otherwise.
HISTORY:
KeithMo 13-Mar-1993 Created.
********************************************************************/
BOOL
GetAnonymousPassword(
CHAR * pszPassword
)
{
BOOL fResult;
NTSTATUS ntStatus;
ANSI_STRING ansiPassword;
PUNICODE_STRING punicodePassword = NULL;
//
// See if we managed to actually open the password secret.
//
if( hPasswordSecret == NULL )
{
return FALSE;
}
//
// Query the secret value.
//
ntStatus = LsaQuerySecret( hPasswordSecret,
&punicodePassword,
NULL,
NULL,
NULL );
if( NT_SUCCESS(ntStatus) )
{
//
// Map it to ANSI.
//
ansiPassword.Buffer = pszPassword;
ansiPassword.Length = 0;
ansiPassword.MaximumLength = PWLEN+1;
ntStatus = RtlUnicodeStringToAnsiString( &ansiPassword,
punicodePassword,
FALSE );
RtlZeroMemory( punicodePassword->Buffer,
punicodePassword->MaximumLength );
}
fResult = NT_SUCCESS(ntStatus);
//
// Cleanup & exit.
//
if( punicodePassword != NULL )
{
LsaFreeMemory( (PVOID)punicodePassword );
}
return fResult;
} // GetAnonymousPassword
/*******************************************************************
NAME: PathAccessCheck
SYNOPSIS: Determine if the specified user has privilege to
access the specified path.
ENTRY: pUserData - The user to check access against. Note
that this parameter may be NULL, in which case
access will always be granted.
pszPath - The canonicalized path to check access to.
access - Specifies the type of access desired.
RETURNS: BOOL - TRUE if user has access, FALSE otherwise.
HISTORY:
KeithMo 07-Mar-1993 Created.
********************************************************************/
BOOL
PathAccessCheck(
USER_DATA * pUserData,
CHAR * pszPath,
ACCESS_TYPE _access
)
{
CHAR chDrive;
DWORD mask;
BOOL fAccessGranted;
BOOL fUserRead;
BOOL fUserWrite;
FTPD_ASSERT( pszPath != NULL );
FTPD_ASSERT( ( _access > FirstAccessType ) && ( _access < LastAccessType ) );
IF_DEBUG( SECURITY )
{
FTPD_PRINT(( "validating %s access for %s\n",
apszAccessTypes[_access],
pszPath ));
}
//
// Get & validate the target drive.
//
chDrive = *pszPath;
if( ( chDrive >= 'a' ) && ( chDrive <= 'z' ) )
{
chDrive -= ( 'a' - 'A' );
}
if( ( chDrive < 'A' ) || ( chDrive > 'Z' ) )
{
FTPD_PRINT(( "PathAccessCheck - bad drive in path %s\n",
pszPath ));
//
// We received a bogus drive letter.
//
return FALSE;
}
//
// Calculate the mask to use against maskReadAccess & maskWriteAccess.
//
mask = 1L << ( chDrive - 'A' );
FTPD_ASSERT( mask != 0 );
FTPD_ASSERT( ( mask & VALID_DOS_DRIVE_MASK ) != 0 );
//
// Perform the actual access check.
//
fAccessGranted = FALSE;
if( pUserData == NULL )
{
fUserRead = TRUE;
fUserWrite = TRUE;
}
else
{
fUserRead = TEST_UF( pUserData, READ_ACCESS );
fUserWrite = TEST_UF( pUserData, WRITE_ACCESS );
}
switch( _access )
{
case ReadAccess :
fAccessGranted = ( maskReadAccess & mask ) != 0;
fAccessGranted &= fUserRead;
break;
case WriteAccess :
fAccessGranted = ( maskWriteAccess & mask ) != 0;
fAccessGranted &= fUserWrite;
break;
case CreateAccess :
fAccessGranted = ( maskWriteAccess & mask ) != 0;
fAccessGranted &= fUserWrite;
break;
case DeleteAccess :
fAccessGranted = ( maskWriteAccess & mask ) != 0;
fAccessGranted &= fUserWrite;
break;
default :
FTPD_PRINT(( "PathAccessCheck - invalid access type %d\n",
_access ));
FTPD_ASSERT( FALSE );
break;
}
IF_DEBUG( SECURITY )
{
FTPD_PRINT(( "access to %s has been %s\n",
pszPath,
fAccessGranted ? "granted" : "denied" ));
}
return fAccessGranted;
} // PathAccessCheck
/*******************************************************************
NAME: UpdateAccessMasks
SYNOPSIS: Scans the currently available drives, removes any
unaccessible or remote drives from the read & write
access masks.
HISTORY:
KeithMo 09-Mar-1993 Created.
********************************************************************/
VOID
UpdateAccessMasks(
VOID
)
{
CHAR szRoot[4] = "d:\\";
CHAR chDrive;
DWORD mask;
UINT res;
mask = (DWORD)~1;
for( chDrive = 'A' ; chDrive <= 'Z' ; chDrive++ )
{
szRoot[0] = chDrive;
res = GetDriveType( szRoot );
switch( res )
{
case 0 : // indeterminate
case 1 : // no root directory
case DRIVE_REMOTE : // network drive
maskReadAccess &= mask;
maskWriteAccess &= mask;
break;
}
mask = ( mask << 1 ) | 1;
}
} // UpdateAccessMasks
/*******************************************************************
NAME: ApiAccessCheck
SYNOPSIS: Impersonate the RPC client, then check for valid
access against our server security object.
ENTRY: maskDesiredAccess - Specifies the desired access mask.
This mask must not contain generic accesses.
RETURNS: APIERR - NO_ERROR if access granted, ERROR_ACCESS_DENIED
if access denied, other Win32 errors if something
tragic happened.
HISTORY:
KeithMo 26-Mar-1993 Created.
********************************************************************/
APIERR
ApiAccessCheck(
ACCESS_MASK maskDesiredAccess
)
{
APIERR err;
NTSTATUS ntStatus;
UNICODE_STRING SubsystemName;
UNICODE_STRING ObjectTypeName;
UNICODE_STRING ObjectName;
BOOLEAN fGenerateOnClose;
NTSTATUS ntAccessStatus;
ACCESS_MASK maskAccessGranted;
RtlInitUnicodeString( &SubsystemName, SUBSYSTEM_NAME );
RtlInitUnicodeString( &ObjectTypeName, OBJECTTYPE_NAME );
RtlInitUnicodeString( &ObjectName, OBJECT_NAME );
//
// Impersonate the RPC client.
//
err = (APIERR)RpcImpersonateClient( NULL );
if( err != NO_ERROR )
{
IF_DEBUG( SECURITY )
{
FTPD_PRINT(( "cannot impersonate rpc client, error %lu\n",
err ));
}
return err;
}
//
// Validate access.
//
ntStatus = NtAccessCheckAndAuditAlarm( &SubsystemName,
NULL,
&ObjectTypeName,
&ObjectName,
sdApiObject,
maskDesiredAccess,
&FtpApiObjectMapping,
FALSE,
&maskAccessGranted,
&ntAccessStatus,
&fGenerateOnClose );
//
// Revert to our former self.
//
err = (APIERR)RpcRevertToSelf();
if( err != NO_ERROR )
{
IF_DEBUG( SECURITY )
{
FTPD_PRINT(( "cannot revert to former self, error %lu\n",
err ));
}
return err;
}
//
// Check the results.
//
err = (APIERR)RtlNtStatusToDosError( ntStatus );
if( !NT_SUCCESS(ntStatus) )
{
IF_DEBUG( SECURITY )
{
FTPD_PRINT(( "cannot check access, error %08lX (mapped to %lu)\n",
ntStatus,
err ));
}
return err;
}
if( ntAccessStatus != STATUS_SUCCESS )
{
err = (APIERR)RtlNtStatusToDosError( ntAccessStatus );
IF_DEBUG( SECURITY )
{
FTPD_PRINT(( "bad access status, error %08lX (mapped to %lu)\n",
ntAccessStatus,
err ));
}
return err;
}
return err;
} // ApiAccessCheck
/*******************************************************************
NAME: DetermineUserAccess
SYNOPSIS: Determines the current user's access to the FTP Server.
This is done by testing different RegOpenKey APIs
against the FTPD_ACCESS_KEY key. This key (if it exists)
will be "under" the FTPD_PARAMETERS_KEY key.
RETURNS: DWORD - Will be an OR combination of UF_READ_ACCESS
and UF_WRITE_ACCESS. If this is zero, then the
user cannot access the FTP Server.
HISTORY:
KeithMo 06-May-1993 Created.
********************************************************************/
DWORD
DetermineUserAccess(
VOID
)
{
DWORD dwAccess = 0;
HKEY hkey;
APIERR err;
//
// Test for read access.
//
err = RegOpenKeyEx( hkeyFtpd,
FTPD_ACCESS_KEY,
0,
KEY_READ,
&hkey );
if( err == NO_ERROR )
{
//
// Success.
//
dwAccess |= UF_READ_ACCESS;
RegCloseKey( hkey );
}
else
if( err == ERROR_FILE_NOT_FOUND )
{
//
// Key doesn't exist.
//
dwAccess |= UF_READ_ACCESS;
}
//
// Test for write access.
//
err = RegOpenKeyEx( hkeyFtpd,
FTPD_ACCESS_KEY,
0,
KEY_WRITE,
&hkey );
if( err == NO_ERROR )
{
//
// Success.
//
dwAccess |= UF_WRITE_ACCESS;
RegCloseKey( hkey );
}
else
if( err == ERROR_FILE_NOT_FOUND )
{
//
// Key doesn't exist.
//
dwAccess |= UF_WRITE_ACCESS;
}
return dwAccess;
} // DetermineUserAccess
//
// Private functions.
//
/*******************************************************************
NAME: OpenPasswordSecret
SYNOPSIS: Opens a handle to the LSA Secret object containing
the anonymous password.
ENTRY: phSecret - Will receive the open handle to the LSA
Secret object.
RETURNS: NTSTATUS - An NT Status code.
HISTORY:
KeithMo 13-Mar-1993 Created.
********************************************************************/
NTSTATUS
OpenPasswordSecret(
LSA_HANDLE * phSecret
)
{
NTSTATUS ntStatus;
OBJECT_ATTRIBUTES ObjectAttributes;
LSA_HANDLE hPolicy;
LSA_HANDLE hSecret = NULL;
//
// Just to be paranoid.
//
FTPD_ASSERT( phSecret != NULL );
*phSecret = NULL;
//
// Open a handle to the local LSA policy.
//
InitializeObjectAttributes( &ObjectAttributes, NULL, 0L, NULL, NULL );
ntStatus = LsaOpenPolicy( NULL,
&ObjectAttributes,
POLICY_ALL_ACCESS,
&hPolicy );
if( NT_SUCCESS( ntStatus ) )
{
UNICODE_STRING unicodeSecretName;
//
// Open the secret object.
//
RtlInitUnicodeString( &unicodeSecretName,
FTPD_ANONYMOUS_SECRET_W );
ntStatus = LsaOpenSecret( hPolicy,
&unicodeSecretName,
SECRET_QUERY_VALUE,
&hSecret );
if( !NT_SUCCESS(ntStatus) )
{
//
// Just to be paranoid...
//
hSecret = NULL;
}
//
// Close the policy handle.
//
LsaClose( hPolicy );
}
//
// Return the (potentially NULL) secret handle.
//
*phSecret = hSecret;
return ntStatus;
} // OpenPasswordSecret
/*******************************************************************
NAME: CreateWellKnownSids
SYNOPSIS: Create some well-known SIDs used to create a security
descriptor for the API security object.
RETURNS: NTSTATUS - An NT Status code.
HISTORY:
KeithMo 26-Mar-1993 Created.
********************************************************************/
NTSTATUS
CreateWellKnownSids(
VOID
)
{
NTSTATUS ntStatus;
SID_IDENTIFIER_AUTHORITY siaWorld = SECURITY_WORLD_SID_AUTHORITY;
SID_IDENTIFIER_AUTHORITY siaNt = SECURITY_NT_AUTHORITY;
ntStatus = RtlAllocateAndInitializeSid( &siaWorld,
1,
SECURITY_WORLD_RID,
0,0,0,0,0,0,0,
&psidWorld );
if( NT_SUCCESS(ntStatus) )
{
ntStatus = RtlAllocateAndInitializeSid( &siaNt,
1,
SECURITY_LOCAL_SYSTEM_RID,
0,0,0,0,0,0,0,
&psidLocalSystem );
}
if( NT_SUCCESS(ntStatus) )
{
ntStatus = RtlAllocateAndInitializeSid( &siaNt,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0,0,0,0,0,0,
&psidAdmins );
}
if( NT_SUCCESS(ntStatus) )
{
ntStatus = RtlAllocateAndInitializeSid( &siaNt,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_SYSTEM_OPS,
0,0,0,0,0,0,
&psidServerOps );
}
if( NT_SUCCESS(ntStatus) )
{
ntStatus = RtlAllocateAndInitializeSid( &siaNt,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_POWER_USERS,
0,0,0,0,0,0,
&psidPowerUsers );
}
IF_DEBUG( SECURITY )
{
if( !NT_SUCCESS(ntStatus) )
{
FTPD_PRINT(( "cannot create well-known sids, error %08lX\n",
ntStatus ));
}
}
return ntStatus;
} // CreateWellKnownSids
/*******************************************************************
NAME: FreeWellKnownSids
SYNOPSIS: Frees the SIDs created with CreateWellKnownSids.
HISTORY:
KeithMo 26-Mar-1993 Created.
********************************************************************/
VOID
FreeWellKnownSids(
VOID
)
{
if( psidWorld != NULL )
{
RtlFreeSid( psidWorld );
psidWorld = NULL;
}
if( psidLocalSystem != NULL )
{
RtlFreeSid( psidLocalSystem );
psidLocalSystem = NULL;
}
if( psidAdmins != NULL )
{
RtlFreeSid( psidAdmins );
psidAdmins = NULL;
}
if( psidServerOps != NULL )
{
RtlFreeSid( psidServerOps );
psidServerOps = NULL;
}
if( psidPowerUsers != NULL )
{
RtlFreeSid( psidPowerUsers );
psidPowerUsers = NULL;
}
} // FreeWellKnownSids
/*******************************************************************
NAME: CreateApiSecurityObject
SYNOPSIS: Create an abstract security object used for validating
user access to the FTP Server APIs.
RETURNS: NTSTATUS - An NT Status code.
HISTORY:
KeithMo 26-Mar-1993 Created.
********************************************************************/
NTSTATUS
CreateApiSecurityObject(
VOID
)
{
NTSTATUS ntStatus;
RTL_ACE_DATA aces[] =
{
{
ACCESS_ALLOWED_ACE_TYPE,
0,
0,
FTPD_ALL_ACCESS,
&psidLocalSystem
},
{
ACCESS_ALLOWED_ACE_TYPE,
0,
0,
FTPD_ALL_ACCESS,
&psidAdmins
},
{
ACCESS_ALLOWED_ACE_TYPE,
0,
0,
FTPD_ALL_ACCESS,
&psidServerOps
},
{
ACCESS_ALLOWED_ACE_TYPE,
0,
0,
FTPD_ALL_ACCESS,
&psidPowerUsers
},
{
ACCESS_ALLOWED_ACE_TYPE,
0,
0,
FTPD_GENERIC_EXECUTE,
&psidWorld
}
};
#define NUM_ACES (sizeof(aces) / sizeof(RTL_ACE_DATA))
ntStatus = RtlCreateUserSecurityObject( aces,
NUM_ACES,
NULL,
NULL,
TRUE,
&FtpApiObjectMapping,
&sdApiObject );
IF_DEBUG( SECURITY )
{
if( !NT_SUCCESS(ntStatus) )
{
FTPD_PRINT(( "cannot create api security object, error %08lX\n",
ntStatus ));
}
}
return ntStatus;
} // CreateApiSecurityObject
/*******************************************************************
NAME: DeleteApiSecurityObject
SYNOPSIS: Frees the security descriptor created with
CreateApiSecurityObject.
HISTORY:
KeithMo 26-Mar-1993 Created.
********************************************************************/
VOID
DeleteApiSecurityObject(
VOID
)
{
RtlDeleteSecurityObject( &sdApiObject );
} // DeleteApiSecurityObject