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.
 
 
 
 
 
 

962 lines
22 KiB

/**********************************************************************/
/** Microsoft Windows NT **/
/** Copyright(c) Microsoft Corp., 1993 **/
/**********************************************************************/
/*
userdb.c
This module manages the user database for the FTPD Service.
FILE HISTORY:
KeithMo 07-Mar-1993 Created.
*/
#include "ftpdp.h"
#pragma hdrstop
//
// Private constants.
//
//
// Private globals.
//
LIST_ENTRY listUserData; // List of user data.
DWORD idNextUser; // Next available user id.
//
// Private prototypes.
//
VOID
CloseUserSockets(
USER_DATA * pUserData,
BOOL fWarnUser
);
DWORD
GetNextUserId(
VOID
);
//
// Public functions.
//
/*******************************************************************
NAME: InitializeUserDatabase
SYNOPSIS: Initializes the user database.
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
InitializeUserDatabase(
VOID
)
{
IF_DEBUG( USER_DATABASE )
{
FTPD_PRINT(( "initializing user database\n" ));
}
//
// Initialize the list of user data.
//
InitializeListHead( &listUserData );
//
// Success!
//
IF_DEBUG( USER_DATABASE )
{
FTPD_PRINT(( "user database initialized\n" ));
}
return NO_ERROR;
} // InitializeUserDatabase
/*******************************************************************
NAME: TerminateUserDatabase
SYNOPSIS: Terminate the user database.
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
TerminateUserDatabase(
VOID
)
{
IF_DEBUG( USER_DATABASE )
{
FTPD_PRINT(( "terminating user database\n" ));
}
FTPD_ASSERT( IsListEmpty( &listUserData ) );
IF_DEBUG( USER_DATABASE )
{
FTPD_PRINT(( "user database terminated\n" ));
}
} // TerminateUserDatabase
/*******************************************************************
NAME: DisconnectUser
SYNOPSIS: Disconnects a specified user.
ENTRY: idUser - Identifies the user to disconnect.
RETURNS: BOOL - TRUE if user found in database, FALSE if
user not found in database.
HISTORY:
KeithMo 09-Mar-1993 Created.
********************************************************************/
BOOL
DisconnectUser(
DWORD idUser
)
{
LIST_ENTRY * plist = listUserData.Flink;
USER_DATA * pUserData;
//
// Synchronize access.
//
LockUserDatabase();
//
// No need to scan an empty database.
//
if( IsListEmpty( &listUserData ) )
{
UnlockUserDatabase();
return FALSE;
}
//
// Scan for the matching user id.
//
for( ; ; )
{
pUserData = CONTAINING_RECORD( plist, USER_DATA, link );
if( pUserData->idUser == idUser )
{
break;
}
plist = plist->Flink;
if( plist == &listUserData )
{
//
// User id not found.
//
UnlockUserDatabase();
return FALSE;
}
}
//
// If we made it this far, then pUserData points to the user data
// associated with idUser.
//
if( pUserData->state != Disconnected )
{
//
// Update statistics.
//
if( pUserData->state == LoggedOn )
{
if( pUserData->dwFlags & UF_ANONYMOUS )
{
DECREMENT_COUNTER( CurrentAnonymousUsers );
}
else
{
DECREMENT_COUNTER( CurrentNonAnonymousUsers );
}
}
//
// Mark the user as disconnected.
//
pUserData->state = Disconnected;
//
// Force close the thread's sockets. This will cause the
// thread to awaken from any blocked socket operation. It
// is the thread's responsibility to do any further cleanup
// (such as calling DeleteUserData).
//
CloseUserSockets( pUserData, TRUE );
}
UnlockUserDatabase();
return TRUE;
} // DisconnectUser
/*******************************************************************
NAME: DisconnectAllUsers
SYNOPSIS: Disconnects all connected users.
HISTORY:
KeithMo 18-Mar-1993 Created.
********************************************************************/
VOID
DisconnectAllUsers(
VOID
)
{
LIST_ENTRY * plist = listUserData.Flink;
//
// Synchronize access.
//
LockUserDatabase();
//
// Disconnect all users.
//
while( plist != &listUserData )
{
USER_DATA * pUserData = CONTAINING_RECORD( plist, USER_DATA, link );
if( pUserData->state != Disconnected )
{
//
// Update statistics.
//
if( pUserData->state == LoggedOn )
{
if( pUserData->dwFlags & UF_ANONYMOUS )
{
DECREMENT_COUNTER( CurrentAnonymousUsers );
}
else
{
DECREMENT_COUNTER( CurrentNonAnonymousUsers );
}
}
//
// Mark the user as disconnected.
//
pUserData->state = Disconnected;
//
// Force close the thread's sockets. This will cause the
// thread to awaken from any blocked socket operation. It
// is the thread's responsibility to do any further cleanup
// (such as calling DeleteUserData).
//
CloseUserSockets( pUserData, TRUE );
}
//
// Advance to next user.
//
plist = plist->Flink;
}
UnlockUserDatabase();
} // DisconnectAllUsers
/*******************************************************************
NAME: DisconnectUsersWithNoAccess
SYNOPSIS: Disconnect all users who do not have read access to
their current directory. This is typically called
after the access masks have changed.
HISTORY:
KeithMo 23-Mar-1993 Created.
********************************************************************/
VOID
DisconnectUsersWithNoAccess(
VOID
)
{
LIST_ENTRY * plist = listUserData.Flink;
//
// Enumerate the connected users & blow some away.
//
LockUserDatabase();
for( ; ; )
{
USER_DATA * pUserData;
//
// Check for end of list.
//
if( plist == &listUserData )
{
break;
}
//
// Get current user, advance to next.
//
pUserData = CONTAINING_RECORD( plist, USER_DATA, link );
plist = plist->Flink;
//
// We're only interested in connected users.
//
if( pUserData->state != LoggedOn )
{
continue;
}
//
// If this user no longer has access to their
// current directory, blow them away.
//
if( !PathAccessCheck( pUserData,
pUserData->szDir,
ReadAccess ) )
{
CHAR * apszSubStrings[2];
IF_DEBUG( SECURITY )
{
FTPD_PRINT(( "User %s (%lu) retroactively denied access to %s\n",
pUserData->szUser,
pUserData->idUser,
pUserData->szDir ));
}
//
// Update statistics.
//
if( pUserData->state == LoggedOn )
{
if( pUserData->dwFlags & UF_ANONYMOUS )
{
DECREMENT_COUNTER( CurrentAnonymousUsers );
}
else
{
DECREMENT_COUNTER( CurrentNonAnonymousUsers );
}
}
//
// Mark the user as disconnected.
//
pUserData->state = Disconnected;
//
// Force close the thread's sockets. This will cause the
// thread to awaken from any blocked socket operation. It
// is the thread's responsibility to do any further cleanup
// (such as calling DeleteUserData).
//
CloseUserSockets( pUserData, TRUE );
//
// Log an event to tell the admin what happened.
//
apszSubStrings[0] = pUserData->szUser;
apszSubStrings[1] = pUserData->szDir;
FtpdLogEvent( FTPD_EVENT_RETRO_ACCESS_DENIED,
2,
apszSubStrings,
0 );
}
}
UnlockUserDatabase();
} // DisconnectUsersWithNoAccess
/*******************************************************************
NAME: CreateUserData
SYNOPSIS: Allocates a new USER_DATA structure.
ENTRY: sControl - The control socket for the new user.
inetHost - The new user's host internet address.
RETURNS: USER_DATA * - Pointer to the newly created user data
structure, NULL if error.
HISTORY:
KeithMo 09-Mar-1993 Created.
********************************************************************/
USER_DATA *
CreateUserData(
SOCKET sControl,
IN_ADDR inetHost
)
{
USER_DATA * pUserData = NULL;
APIERR err = NO_ERROR;
SOCKADDR_IN saddrLocal;
INT cbLocal;
//
// Determine the local address for this connection.
//
cbLocal = sizeof(saddrLocal);
if( getsockname( sControl, (SOCKADDR *)&saddrLocal, &cbLocal ) != 0 )
{
err = (APIERR)WSAGetLastError();
FTPD_ASSERT( err != NO_ERROR );
}
//
// Allocate a new user structure.
//
if( err == NO_ERROR )
{
pUserData = (USER_DATA *)FTPD_ALLOC( sizeof(USER_DATA) );
if( pUserData == NULL )
{
err = GetLastError();
FTPD_ASSERT( err != NO_ERROR );
}
}
//
// Check for errors.
//
if( err != NO_ERROR )
{
if( pUserData != NULL )
{
FTPD_FREE( pUserData );
pUserData = NULL;
}
return NULL;
}
FTPD_ASSERT( pUserData != NULL );
RtlZeroMemory( pUserData, sizeof(USER_DATA) );
//
// Initialize the fields.
//
pUserData->sControl = sControl;
pUserData->sData = INVALID_SOCKET;
pUserData->state = Embryonic;
pUserData->idUser = GetNextUserId();
pUserData->tConnect = GetFtpTime();
pUserData->tAccess = pUserData->tConnect;
pUserData->xferType = AsciiType;
pUserData->xferMode = StreamMode;
pUserData->inetLocal = saddrLocal.sin_addr;
pUserData->inetHost = inetHost;
pUserData->inetData = inetHost;
pUserData->portData = portFtpData;
pUserData->hDir = INVALID_HANDLE_VALUE;
strcpy( pUserData->szDir, pszHomeDir );
if( fMsdosDirOutput )
{
pUserData->dwFlags |= UF_MSDOS_DIR_OUTPUT;
}
if( fAnnotateDirs )
{
pUserData->dwFlags |= UF_ANNOTATE_DIRS;
}
//
// The following initializations are unnecessary due to the
// explicit RtlZeroMemory() call above.
//
// pUserData->link.Flink = NULL;
// pUserData->link.Blink = NULL;
// pUserData->dwFlags = 0;
// pUserData->hToken = NULL;
// pUserData->pIoBuffer = NULL;
//
// strcpy( pUserData->szUser, "" );
//
// for( i = 0 ; i < 26 ; i++ )
// {
// pUserData->apszDirs[i] = NULL;
// }
//
// Add the structure to the database.
//
LockUserDatabase();
InsertTailList( &listUserData, &pUserData->link );
UnlockUserDatabase();
//
// Success!
//
IF_DEBUG( USER_DATABASE )
{
FTPD_PRINT(( "user %lu created\n",
pUserData->idUser ));
}
return pUserData;
} // CreateUserData
/*******************************************************************
NAME: DeleteUserData
SYNOPSIS: Deletes the current thread's USER_DATA structure and
performs any necessary cleanup on its fields. For
example, the impersonation token is deleted and any
open sockets are closed.
ENTRY: pUserData - The user data structure to delete.
HISTORY:
KeithMo 09-Mar-1993 Created.
********************************************************************/
VOID
DeleteUserData(
USER_DATA * pUserData
)
{
INT i;
//
// This may get called before the tls was fully initialized.
//
if( pUserData == NULL )
{
return;
}
//
// Let's get a little paranoid.
//
IF_DEBUG( USER_DATABASE )
{
FTPD_PRINT(( "deleting user %lu\n",
pUserData->idUser ));
}
//
// Remove the structure from the database & the tls.
//
LockUserDatabase();
RemoveEntryList( &pUserData->link );
UnlockUserDatabase();
TlsSetValue( tlsUserData, NULL );
//
// Update the statistics.
//
if( pUserData->state == LoggedOn )
{
if( pUserData->dwFlags & UF_ANONYMOUS )
{
DECREMENT_COUNTER( CurrentAnonymousUsers );
}
else
{
DECREMENT_COUNTER( CurrentNonAnonymousUsers );
}
}
//
// Close any open sockets & handles.
//
CloseUserSockets( pUserData, FALSE );
if( pUserData->hToken != NULL )
{
if( pUserData->hToken != hAnonymousToken )
{
DeleteUserToken( pUserData->hToken );
}
pUserData->hToken = NULL;
}
if( pUserData->hDir != INVALID_HANDLE_VALUE )
{
IF_DEBUG( VIRTUAL_IO )
{
FTPD_PRINT(( "closing directory handle %08lX\n",
pUserData->hDir ));
}
NtClose( pUserData->hDir );
pUserData->hDir = INVALID_HANDLE_VALUE;
}
//
// Release the memory allocated to this structure.
//
if( pUserData->pIoBuffer != NULL )
{
FTPD_FREE( pUserData->pIoBuffer );
pUserData->pIoBuffer = NULL;
}
if( pUserData->pszRename != NULL )
{
FTPD_FREE( pUserData->pszRename );
pUserData->pszRename = NULL;
}
for( i = 0 ; i < 26 ; i++ )
{
if( pUserData->apszDirs[i] != NULL )
{
FTPD_FREE( pUserData->apszDirs[i] );
pUserData->apszDirs[i] = NULL;
}
}
FTPD_FREE( pUserData );
} // DeleteUserData
/*******************************************************************
NAME: EnumerateUser
SYNOPSIS: Enumerates the current active users into the specified
buffer.
ENTRY: pvEnum - Will receive the number of entries and a
pointer to the enumeration buffer.
pcbBuffer - On entry, points to a DWORD containing the
size (in BYTEs) of the enumeration buffer. Will
receive the necessary buffer size to enumerate
all users.
RETURNS: BOOL - TRUE if enumeration successful (all connected
users stored in buffer), FALSE otherwise.
NOTES: This MUST be called with the user database lock held!
HISTORY:
KeithMo 24-Mar-1993 Created.
********************************************************************/
BOOL
EnumerateUsers(
VOID * pvEnum,
DWORD * pcbBuffer
)
{
FTP_USER_ENUM_STRUCT * pEnum;
FTP_USER_INFO * pUserInfo;
LIST_ENTRY * pList;
WCHAR * pszNext;
DWORD cEntries;
DWORD cbRequired;
DWORD cbBuffer;
DWORD timeNow;
BOOL fResult;
FTPD_ASSERT( pcbBuffer != NULL );
//
// Setup.
//
cEntries = 0;
cbRequired = 0;
cbBuffer = *pcbBuffer;
fResult = TRUE;
pEnum = (FTP_USER_ENUM_STRUCT *)pvEnum;
pUserInfo = pEnum->Buffer;
pList = listUserData.Flink;
pszNext = (WCHAR *)( (BYTE *)pUserInfo + cbBuffer );
timeNow = GetFtpTime();
//
// Scan the users.
//
for( ; ; )
{
USER_DATA * pUserData;
DWORD cbUserName;
//
// Check for end of user list.
//
if( pList == &listUserData )
{
break;
}
//
// Get current user, advance to next.
//
pUserData = CONTAINING_RECORD( pList, USER_DATA, link );
pList = pList->Flink;
//
// We're only interested in connected users.
//
if( ( pUserData->state == Embryonic ) ||
( pUserData->state == Disconnected ) )
{
continue;
}
//
// Determine required buffer size for current user.
//
cbUserName = ( strlen( pUserData->szUser ) + 1 ) * sizeof(WCHAR);
cbRequired += cbUserName + sizeof(FTP_USER_INFO);
//
// If there's room for the user data, store it.
//
if( fResult && ( cbRequired <= cbBuffer ) )
{
pszNext -= ( cbUserName / sizeof(WCHAR) );
FTPD_ASSERT( (BYTE *)pszNext >=
( (BYTE *)pUserInfo + sizeof(FTP_USER_INFO) ) );
pUserInfo->idUser = pUserData->idUser;
pUserInfo->pszUser = pszNext;
pUserInfo->fAnonymous = ( pUserData->dwFlags & UF_ANONYMOUS ) != 0;
pUserInfo->inetHost = (DWORD)pUserData->inetHost.s_addr;
pUserInfo->tConnect = timeNow - pUserData->tConnect;
if( !MultiByteToWideChar( CP_OEMCP,
0,
pUserData->szUser,
-1,
pszNext,
(int)cbUserName ) )
{
FTPD_PRINT(( "MultiByteToWideChar failed???\n" ));
fResult = FALSE;
}
else
{
pUserInfo++;
cEntries++;
}
}
else
{
fResult = FALSE;
}
}
//
// Update enum buffer header.
//
pEnum->EntriesRead = cEntries;
*pcbBuffer = cbRequired;
return fResult;
} // EnumerateUsers
//
// Private functions.
//
/*******************************************************************
NAME: CloseUserSockets
SYNOPSIS: Closes any sockets opened by the user.
ENTRY: pUserData - The USER_DATA structure to destroy.
fWarnUser - If TRUE, send the user a warning shot
before closing sockets.
HISTORY:
KeithMo 10-Mar-1993 Created.
********************************************************************/
VOID
CloseUserSockets(
USER_DATA * pUserData,
BOOL fWarnUser
)
{
SOCKET sData;
SOCKET sControl;
FTPD_ASSERT( pUserData != NULL );
//
// Close any open sockets. It is very important to set
// sData & sControl to INVALID_SOCKET *before* we actually
// close the sockets. Since this routine is called to
// disconnect a user, and may be called from the RPC thread,
// closing one of the sockets may cause the client thread
// to unblock and try to access the socket. Setting the
// values in the per-user area to INVALID_SOCKET before
// closing the sockets prevents keeps this from being a
// problem.
//
sData = pUserData->sData;
sControl = pUserData->sControl;
pUserData->sData = INVALID_SOCKET;
pUserData->sControl = INVALID_SOCKET;
if( sData != INVALID_SOCKET )
{
ResetSocket( sData );
}
if( sControl != INVALID_SOCKET )
{
if( fWarnUser )
{
//
// Since this may be called in a context other than
// the user we're disconnecting, we cannot rely
// on the USER_DATA fields. So, we cannot call
// SockReply, so we'll kludge one together with
// SockPrintf2.
//
SockPrintf2( sControl,
"%d Terminating connection.",
REPLY_SERVICE_NOT_AVAILABLE );
}
CloseSocket( sControl );
}
} // CloseUserSockets
/*******************************************************************
NAME: GetNextUserId
SYNOPSIS: Returns the next available user id.
RETURNS: DWORD - The user id.
HISTORY:
KeithMo 23-Mar-1993 Created.
********************************************************************/
DWORD
GetNextUserId(
VOID
)
{
LockGlobals();
if( ++idNextUser == 0 )
{
idNextUser++;
}
UnlockGlobals();
return idNextUser;
} // GetNextUserId