mirror of https://github.com/lianthony/NT4.0
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
1653 lines
42 KiB
/**********************************************************************/
|
|
/** Microsoft Windows NT **/
|
|
/** Copyright(c) Microsoft Corp., 1993 **/
|
|
/**********************************************************************/
|
|
|
|
/*
|
|
virtual.c
|
|
|
|
This module contains the virtual I/O package.
|
|
|
|
Under Win32, the "current directory" is an attribute of a process,
|
|
not a thread. This causes some grief for the FTPD service, since
|
|
it is impersonating users on the server side. The users must
|
|
"think" they can change current directory at will. We'll provide
|
|
this behaviour in this package.
|
|
|
|
|
|
FILE HISTORY:
|
|
KeithMo 09-Mar-1993 Created.
|
|
|
|
*/
|
|
|
|
|
|
#include "ftpdp.h"
|
|
#pragma hdrstop
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
|
|
|
|
//
|
|
// Private constants.
|
|
//
|
|
|
|
#define ACTION_NOTHING 0x00000000
|
|
#define ACTION_EMIT_CH 0x00010000
|
|
#define ACTION_EMIT_DOT_CH 0x00020000
|
|
#define ACTION_EMIT_DOT_DOT_CH 0x00030000
|
|
#define ACTION_BACKUP 0x00040000
|
|
#define ACTION_MASK 0xFFFF0000
|
|
|
|
#define LOG_FILE_RETRIES 2
|
|
|
|
|
|
//
|
|
// Private globals.
|
|
//
|
|
|
|
CRITICAL_SECTION csLogFileLock;
|
|
|
|
INT StateTable[4][4] =
|
|
{
|
|
{ // state 0
|
|
1 | ACTION_EMIT_CH, // "\"
|
|
0 | ACTION_EMIT_CH, // "."
|
|
4 | ACTION_EMIT_CH, // EOS
|
|
0 | ACTION_EMIT_CH // other
|
|
},
|
|
|
|
{ // state 1
|
|
1 | ACTION_NOTHING, // "\"
|
|
2 | ACTION_NOTHING, // "."
|
|
4 | ACTION_EMIT_CH, // EOS
|
|
0 | ACTION_EMIT_CH // other
|
|
},
|
|
|
|
{ // state 2
|
|
1 | ACTION_NOTHING, // "\"
|
|
3 | ACTION_NOTHING, // "."
|
|
4 | ACTION_EMIT_CH, // EOS
|
|
0 | ACTION_EMIT_DOT_CH // other
|
|
},
|
|
|
|
{ // state 3
|
|
1 | ACTION_BACKUP, // "\"
|
|
0 | ACTION_EMIT_DOT_DOT_CH, // "."
|
|
4 | ACTION_BACKUP, // EOS
|
|
0 | ACTION_EMIT_DOT_DOT_CH // other
|
|
}
|
|
};
|
|
|
|
|
|
//
|
|
// Private prototypes.
|
|
//
|
|
|
|
VOID
|
|
VirtualpLogFileAccess(
|
|
USER_DATA * pUserData,
|
|
CHAR * pszAction,
|
|
CHAR * pszPath
|
|
);
|
|
|
|
VOID
|
|
VirtualpSanitizePath(
|
|
CHAR * pszPath
|
|
);
|
|
|
|
|
|
//
|
|
// Public functions.
|
|
//
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: InitializeVirtualIO
|
|
|
|
SYNOPSIS: Initializes the virtual I/O package.
|
|
|
|
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 09-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
APIERR
|
|
InitializeVirtualIO(
|
|
VOID
|
|
)
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "initializing virtual i/o\n" ));
|
|
}
|
|
|
|
//
|
|
// Initialize the locks.
|
|
//
|
|
|
|
InitializeCriticalSection( &csLogFileLock );
|
|
|
|
//
|
|
// Success!
|
|
//
|
|
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "virtual i/o initialized\n" ));
|
|
}
|
|
|
|
return NO_ERROR;
|
|
|
|
} // InitializeVirtualIO
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: TerminateVirtualIO
|
|
|
|
SYNOPSIS: Terminate the virtual I/O package.
|
|
|
|
NOTES: This routine may only be called by a single thread
|
|
of execution; it is not necessarily multi-thread safe.
|
|
|
|
HISTORY:
|
|
KeithMo 09-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
VOID
|
|
TerminateVirtualIO(
|
|
VOID
|
|
)
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "terminating virtual i/o\n" ));
|
|
}
|
|
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "virtual i/o terminated\n" ));
|
|
}
|
|
|
|
} // TerminateVirtualIO
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: VirtualCanonicalize
|
|
|
|
SYNOPSIS: Canonicalize a path, taking into account the current
|
|
user's (i.e., current thread's) current directory
|
|
value.
|
|
|
|
ENTRY: pUserData - The user initiating the request.
|
|
|
|
pszDest - Will receive the canonicalized path. This
|
|
buffer must be at least MAX_PATH characters long.
|
|
|
|
pszSrc - The path to canonicalize.
|
|
|
|
access - Access type for this path (read, write, etc).
|
|
|
|
RETURNS: APIERR - NO_ERROR if successful, otherwise a Win32
|
|
error code.
|
|
|
|
HISTORY:
|
|
KeithMo 09-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
APIERR
|
|
VirtualCanonicalize(
|
|
USER_DATA * pUserData,
|
|
CHAR * pszDest,
|
|
CHAR * pszSrc,
|
|
ACCESS_TYPE _access
|
|
)
|
|
{
|
|
CHAR szRoot[] = "d:\\";
|
|
CHAR * pszNewDir = NULL;
|
|
APIERR err = NO_ERROR;
|
|
INT iDrive = -1;
|
|
|
|
FTPD_ASSERT( pUserData != NULL );
|
|
|
|
//
|
|
// Move to the user's current directory.
|
|
//
|
|
|
|
if( pszSrc[1] == ':' )
|
|
{
|
|
CHAR chDrive = toupper(*pszSrc);
|
|
|
|
iDrive = (INT)( chDrive - 'A' );
|
|
|
|
if( ( iDrive < 0 ) || ( iDrive >= 26 ) )
|
|
{
|
|
//
|
|
// Bogus drive letter.
|
|
//
|
|
|
|
err = ERROR_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
else
|
|
if( !PathAccessCheck( pUserData,
|
|
pszSrc,
|
|
_access ) )
|
|
{
|
|
//
|
|
// Inaccessible disk volume.
|
|
//
|
|
|
|
err = ERROR_ACCESS_DENIED;
|
|
goto Cleanup;
|
|
}
|
|
else
|
|
if( pUserData->apszDirs[iDrive] == NULL )
|
|
{
|
|
//
|
|
// Valid & accessible volume, first time
|
|
// we've touched it. Drive dir == root.
|
|
//
|
|
|
|
pszNewDir = szRoot;
|
|
*pszNewDir = chDrive;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Valid & accessible volume, we've seen
|
|
// this one before. Drive dir == current.
|
|
//
|
|
|
|
pszNewDir = pUserData->apszDirs[iDrive];
|
|
}
|
|
|
|
//
|
|
// Advance past the drive letter & colon.
|
|
//
|
|
|
|
pszSrc += 2;
|
|
}
|
|
else
|
|
{
|
|
pszNewDir = pUserData->szDir;
|
|
}
|
|
|
|
//
|
|
// At this point, pszNewDir contains the current directory of
|
|
// the target drive.
|
|
//
|
|
|
|
FTPD_ASSERT( pszNewDir != NULL );
|
|
|
|
strcpy( pszDest, pszNewDir );
|
|
|
|
if( IS_PATH_SEP( *pszSrc ) )
|
|
{
|
|
strcpy( pszDest + 2, pszSrc );
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This is a relative path.
|
|
//
|
|
|
|
if( strlen( pszDest ) > 3 )
|
|
{
|
|
strcat( pszDest, "\\" );
|
|
}
|
|
|
|
strcat( pszDest, pszSrc );
|
|
}
|
|
|
|
//
|
|
// At this point, pszDest should contain a fully qualified
|
|
// path to the target file. Only return success if
|
|
// the qualified path doesn't begin with a path
|
|
// separator (indicating a UNC or other funky path).
|
|
//
|
|
|
|
if( IS_PATH_SEP( *pszDest ) )
|
|
{
|
|
err = ERROR_INVALID_PARAMETER;
|
|
}
|
|
else
|
|
if( !PathAccessCheck( pUserData,
|
|
pszDest,
|
|
_access ) )
|
|
{
|
|
err = ERROR_ACCESS_DENIED;
|
|
}
|
|
else
|
|
{
|
|
VirtualpSanitizePath( pszDest );
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
if( err != NO_ERROR )
|
|
{
|
|
FTPD_PRINT(( "cannot canonicalize %s - %s, error %lu\n",
|
|
pUserData->szDir,
|
|
pszSrc,
|
|
err ));
|
|
}
|
|
}
|
|
|
|
return err;
|
|
|
|
} // VirtualCanonicalize
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: VirtualCreateFile
|
|
|
|
SYNOPSIS: Creates a new (or overwrites an existing) file.
|
|
|
|
ENTRY: pUserData - The user initiating the request.
|
|
|
|
phFile - Will receive the file handle. Will be
|
|
INVALID_HANDLE_VALUE if an error occurs.
|
|
|
|
pszFile - The name of the new file.
|
|
|
|
fAppend - If TRUE, and pszFile already exists, then
|
|
append to the existing file. Otherwise, create
|
|
a new file. Note that FALSE will ALWAYS create
|
|
a new file, potentially overwriting an existing
|
|
file.
|
|
|
|
RETURNS: APIERR - NO_ERROR if successful, otherwise a Win32
|
|
error code.
|
|
|
|
HISTORY:
|
|
KeithMo 09-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
APIERR
|
|
VirtualCreateFile(
|
|
USER_DATA * pUserData,
|
|
HANDLE * phFile,
|
|
CHAR * pszFile,
|
|
BOOL fAppend
|
|
)
|
|
{
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
APIERR err;
|
|
CHAR szCanonPath[MAX_PATH];
|
|
|
|
FTPD_ASSERT( pUserData != NULL );
|
|
FTPD_ASSERT( phFile != NULL );
|
|
FTPD_ASSERT( pszFile != NULL );
|
|
|
|
err = VirtualCanonicalize( pUserData, szCanonPath, pszFile, CreateAccess );
|
|
|
|
if( err == NO_ERROR )
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "creating %s\n", szCanonPath ));
|
|
}
|
|
|
|
hFile = CreateFile( szCanonPath,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
fAppend ? OPEN_ALWAYS : CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL );
|
|
|
|
if( hFile == INVALID_HANDLE_VALUE )
|
|
{
|
|
err = GetLastError();
|
|
}
|
|
|
|
if( fAppend && ( err == NO_ERROR ) )
|
|
{
|
|
if( SetFilePointer( hFile,
|
|
0,
|
|
NULL,
|
|
FILE_END ) == (DWORD)-1L )
|
|
{
|
|
err = GetLastError();
|
|
|
|
CloseHandle( hFile );
|
|
hFile = INVALID_HANDLE_VALUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( err == 0 )
|
|
{
|
|
VirtualpLogFileAccess( pUserData,
|
|
fAppend ? "appended" : "created",
|
|
szCanonPath );
|
|
}
|
|
else
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "cannot create %s, error %lu\n",
|
|
szCanonPath,
|
|
err ));
|
|
}
|
|
}
|
|
|
|
*phFile = hFile;
|
|
|
|
return err;
|
|
|
|
} // VirtualCreateFile
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: VirtualCreateUniqueFile
|
|
|
|
SYNOPSIS: Creates a new unique (temporary) file in the current
|
|
virtual directory.
|
|
|
|
ENTRY: pUserData - The user initiating the request.
|
|
|
|
phFile - Will receive the file handle. Will be
|
|
INVALID_HANDLE_VALUE if an error occurs.
|
|
|
|
pszTmpFile - Will receive the name of the temporary
|
|
file. This buffer MUST be at least MAX_PATH
|
|
characters long.
|
|
|
|
RETURNS: APIERR - NO_ERROR if successful, otherwise a Win32
|
|
error code.
|
|
|
|
HISTORY:
|
|
KeithMo 16-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
APIERR
|
|
VirtualCreateUniqueFile(
|
|
USER_DATA * pUserData,
|
|
HANDLE * phFile,
|
|
CHAR * pszTmpFile
|
|
)
|
|
{
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
APIERR err = NO_ERROR;
|
|
|
|
FTPD_ASSERT( pUserData != NULL );
|
|
FTPD_ASSERT( phFile != NULL );
|
|
FTPD_ASSERT( pszTmpFile != NULL );
|
|
|
|
if( GetTempFileName( pUserData->szDir, "FTPD", 0, pszTmpFile ) == 0 )
|
|
{
|
|
err = GetLastError();
|
|
}
|
|
|
|
if( err == NO_ERROR )
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "creating unique file %s\n", pszTmpFile ));
|
|
}
|
|
|
|
hFile = CreateFile( pszTmpFile,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL );
|
|
|
|
if( hFile == INVALID_HANDLE_VALUE )
|
|
{
|
|
err = GetLastError();
|
|
}
|
|
}
|
|
|
|
if( err == 0 )
|
|
{
|
|
VirtualpLogFileAccess( pUserData,
|
|
"created",
|
|
pszTmpFile );
|
|
}
|
|
else
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "cannot create unique file, error %lu\n",
|
|
err ));
|
|
}
|
|
}
|
|
|
|
*phFile = hFile;
|
|
|
|
return err;
|
|
|
|
} // VirtualCreateUniqueFile
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: VirtualOpenFile
|
|
|
|
SYNOPSIS: Opens an existing file.
|
|
|
|
ENTRY: pUserData - The user initiating the request.
|
|
|
|
phFile - Will receive the file handle. Will be
|
|
INVALID_HANDLE_VALUE if an error occurs.
|
|
|
|
pszFile - The name of the existing file.
|
|
|
|
RETURNS: APIERR - NO_ERROR if successful, otherwise a Win32
|
|
error code.
|
|
|
|
HISTORY:
|
|
KeithMo 09-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
APIERR
|
|
VirtualOpenFile(
|
|
USER_DATA * pUserData,
|
|
HANDLE * phFile,
|
|
CHAR * pszFile
|
|
)
|
|
{
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
APIERR err;
|
|
CHAR szCanonPath[MAX_PATH];
|
|
|
|
FTPD_ASSERT( pUserData != NULL );
|
|
FTPD_ASSERT( phFile != NULL );
|
|
FTPD_ASSERT( pszFile != NULL );
|
|
|
|
err = VirtualCanonicalize( pUserData, szCanonPath, pszFile, ReadAccess );
|
|
|
|
if( err == NO_ERROR )
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "opening %s\n", szCanonPath ));
|
|
}
|
|
|
|
hFile = CreateFile( szCanonPath,
|
|
GENERIC_READ,
|
|
FILE_SHARE_DELETE |
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL
|
|
| FILE_FLAG_SEQUENTIAL_SCAN,
|
|
NULL );
|
|
|
|
if( hFile == INVALID_HANDLE_VALUE )
|
|
{
|
|
err = GetLastError();
|
|
}
|
|
}
|
|
|
|
if( err == 0 )
|
|
{
|
|
VirtualpLogFileAccess( pUserData,
|
|
"opened",
|
|
szCanonPath );
|
|
}
|
|
else
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "cannot open %s, error %lu\n",
|
|
pszFile,
|
|
err ));
|
|
}
|
|
}
|
|
|
|
*phFile = hFile;
|
|
|
|
return err;
|
|
|
|
} // VirtualOpenFile
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: Virtual_fopen
|
|
|
|
SYNOPSIS: Opens an file stream.
|
|
|
|
ENTRY: pUserData - The user initiating the request.
|
|
|
|
pszFile - The name of the file to open.
|
|
|
|
RETURNS: FILE * - The open file stream, NULL if file cannot
|
|
be opened.
|
|
|
|
NOTES: Since this is only used for accessing the ~FTPSVC~.CKM
|
|
annotation files, we don't log file accesses here.
|
|
|
|
HISTORY:
|
|
KeithMo 07-May-1993 Created.
|
|
|
|
********************************************************************/
|
|
FILE *
|
|
Virtual_fopen(
|
|
USER_DATA * pUserData,
|
|
CHAR * pszFile
|
|
)
|
|
{
|
|
FILE * pfile = NULL;
|
|
APIERR err;
|
|
HANDLE hFile;
|
|
INT idFile;
|
|
CHAR szCanonPath[MAX_PATH];
|
|
|
|
FTPD_ASSERT( pUserData != NULL );
|
|
FTPD_ASSERT( pszFile != NULL );
|
|
|
|
err = VirtualCanonicalize( pUserData,
|
|
szCanonPath,
|
|
pszFile,
|
|
ReadAccess );
|
|
|
|
if( err == NO_ERROR )
|
|
{
|
|
//
|
|
// Note that the fopen() C Run Time function grabs a number of
|
|
// locks to perform the open. This is expensive, especially in
|
|
// a heavily threaded application like the FTP Server. To avoid
|
|
// the CRT as much as possible, we'll CreateFile() ourselves.
|
|
// If that fails, so be it. If it succeeds, we can then create
|
|
// the I/O stream a'la fopen().
|
|
//
|
|
|
|
hFile = CreateFile( szCanonPath,
|
|
GENERIC_READ,
|
|
FILE_SHARE_DELETE |
|
|
FILE_SHARE_READ |
|
|
FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL |
|
|
FILE_FLAG_SEQUENTIAL_SCAN,
|
|
NULL );
|
|
|
|
if( hFile != INVALID_HANDLE_VALUE )
|
|
{
|
|
idFile = _open_osfhandle( (LONG)hFile, _O_RDONLY | _O_TEXT );
|
|
|
|
if( idFile != -1 )
|
|
{
|
|
pfile = _fdopen( idFile, "r" );
|
|
|
|
if( pfile != NULL )
|
|
{
|
|
return pfile;
|
|
}
|
|
|
|
_close( idFile );
|
|
}
|
|
|
|
CloseHandle( hFile );
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
|
|
} // Virtual_fopen
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: VirtualFindFirstFile
|
|
|
|
SYNOPSIS: Searches for a matching file in a directory.
|
|
|
|
ENTRY: pUserData - The user initiating the request.
|
|
|
|
phSearch - Will receive the search handle. Will be
|
|
INVALID_HANDLE_VALUE if an error occurs.
|
|
|
|
pszSearchFile - The name of the file to search for.
|
|
|
|
pFindData - Will receive find information.
|
|
|
|
RETURNS: APIERR - NO_ERROR if successful, otherwise a Win32
|
|
error code.
|
|
|
|
HISTORY:
|
|
KeithMo 10-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
APIERR
|
|
VirtualFindFirstFile(
|
|
USER_DATA * pUserData,
|
|
HANDLE * phSearch,
|
|
CHAR * pszSearchFile,
|
|
WIN32_FIND_DATA * pFindData
|
|
)
|
|
{
|
|
HANDLE hSearch = INVALID_HANDLE_VALUE;
|
|
APIERR err;
|
|
CHAR szCanonSearchFile[MAX_PATH];
|
|
|
|
FTPD_ASSERT( pUserData != NULL );
|
|
FTPD_ASSERT( phSearch != NULL );
|
|
FTPD_ASSERT( pszSearchFile != NULL );
|
|
FTPD_ASSERT( pFindData != NULL );
|
|
|
|
err = VirtualCanonicalize( pUserData,
|
|
szCanonSearchFile,
|
|
pszSearchFile,
|
|
ReadAccess );
|
|
|
|
if( err == NO_ERROR )
|
|
{
|
|
//
|
|
// GetFullPathName (called by VirtualCanonicalize)
|
|
// will strip trailing dots from the path. Replace them here.
|
|
//
|
|
|
|
if( ( strpbrk( pszSearchFile, "?*" ) != NULL ) &&
|
|
( szCanonSearchFile[strlen(szCanonSearchFile)-1] == '.' ) )
|
|
{
|
|
strcat( szCanonSearchFile, "." );
|
|
}
|
|
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "searching for %s\n", szCanonSearchFile ));
|
|
}
|
|
|
|
hSearch = FindFirstFile( szCanonSearchFile,
|
|
pFindData );
|
|
|
|
if( hSearch == INVALID_HANDLE_VALUE )
|
|
{
|
|
err = GetLastError();
|
|
}
|
|
}
|
|
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
if( err != NO_ERROR )
|
|
{
|
|
FTPD_PRINT(( "cannot search for %s, error %lu\n",
|
|
pszSearchFile,
|
|
err ));
|
|
}
|
|
}
|
|
|
|
*phSearch = hSearch;
|
|
|
|
return err;
|
|
|
|
} // VirtualFindFirstFile
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: VirtualDeleteFile
|
|
|
|
SYNOPSIS: Deletes an existing file.
|
|
|
|
ENTRY: pUserData - The user initiating the request.
|
|
|
|
pszFile - The name of the file.
|
|
|
|
RETURNS: APIERR - NO_ERROR if successful, otherwise a Win32
|
|
error code.
|
|
|
|
HISTORY:
|
|
KeithMo 09-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
APIERR
|
|
VirtualDeleteFile(
|
|
USER_DATA * pUserData,
|
|
CHAR * pszFile
|
|
)
|
|
{
|
|
APIERR err;
|
|
CHAR szCanonPath[MAX_PATH];
|
|
|
|
FTPD_ASSERT( pUserData != NULL );
|
|
|
|
//
|
|
// We'll canonicalize the path, asking for *read* access. If
|
|
// the path canonicalizes correctly, we'll then try to open the
|
|
// file to ensure it exists. Only then will we check for delete
|
|
// access to the path. This mumbo-jumbo is necessary to get the
|
|
// proper error codes if someone trys to delete a nonexistent
|
|
// file on a read-only volume.
|
|
//
|
|
|
|
err = VirtualCanonicalize( pUserData,
|
|
szCanonPath,
|
|
pszFile,
|
|
ReadAccess );
|
|
|
|
if( err == NO_ERROR )
|
|
{
|
|
HANDLE hFile;
|
|
|
|
hFile = CreateFile( szCanonPath,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL );
|
|
|
|
if( hFile == INVALID_HANDLE_VALUE )
|
|
{
|
|
err = GetLastError();
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// The file DOES exist. Close the handle, then check
|
|
// to ensure we really have delete access.
|
|
//
|
|
|
|
CloseHandle( hFile );
|
|
|
|
if( !PathAccessCheck( pUserData,
|
|
szCanonPath,
|
|
DeleteAccess ) )
|
|
{
|
|
err = ERROR_ACCESS_DENIED;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( err == NO_ERROR )
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "deleting %s\n", szCanonPath ));
|
|
}
|
|
|
|
if( !DeleteFile( szCanonPath ) )
|
|
{
|
|
err = GetLastError();
|
|
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "cannot delete %s, error %lu\n",
|
|
szCanonPath,
|
|
err ));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "cannot canonicalize %s - %s, error %lu\n",
|
|
pUserData->szDir,
|
|
pszFile,
|
|
err ));
|
|
}
|
|
}
|
|
|
|
return err;
|
|
|
|
} // VirtualDeleteFile
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: VirtualRenameFile
|
|
|
|
SYNOPSIS: Renames an existing file or directory.
|
|
|
|
ENTRY: pUserData - The user initiating the request.
|
|
|
|
pszExisting - The name of an existing file or directory.
|
|
|
|
pszNew - The new name for the file or directory.
|
|
|
|
RETURNS: APIERR - NO_ERROR if successful, otherwise a Win32
|
|
error code.
|
|
|
|
HISTORY:
|
|
KeithMo 10-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
APIERR
|
|
VirtualRenameFile(
|
|
USER_DATA * pUserData,
|
|
CHAR * pszExisting,
|
|
CHAR * pszNew
|
|
)
|
|
{
|
|
APIERR err;
|
|
CHAR szCanonExisting[MAX_PATH];
|
|
CHAR szCanonNew[MAX_PATH];
|
|
|
|
FTPD_ASSERT( pUserData != NULL );
|
|
|
|
err = VirtualCanonicalize( pUserData,
|
|
szCanonExisting,
|
|
pszExisting,
|
|
DeleteAccess );
|
|
|
|
if( err == NO_ERROR )
|
|
{
|
|
err = VirtualCanonicalize( pUserData,
|
|
szCanonNew,
|
|
pszNew,
|
|
CreateAccess );
|
|
|
|
if( err == NO_ERROR )
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "renaming %s to %s\n",
|
|
szCanonExisting,
|
|
szCanonNew ));
|
|
}
|
|
|
|
if( !MoveFileEx( szCanonExisting,
|
|
szCanonNew,
|
|
MOVEFILE_REPLACE_EXISTING ) )
|
|
{
|
|
err = GetLastError();
|
|
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "cannot rename %s to %s, error %lu\n",
|
|
szCanonExisting,
|
|
szCanonNew,
|
|
err ));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "cannot canonicalize %s - %s, error %lu\n",
|
|
pUserData->szDir,
|
|
pszExisting,
|
|
err ));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "cannot canonicalize %s - %s, error %lu\n",
|
|
pUserData->szDir,
|
|
pszExisting,
|
|
err ));
|
|
}
|
|
}
|
|
|
|
return err;
|
|
|
|
} // VirtualRenameFile
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: VirtualChDir
|
|
|
|
SYNOPSIS: Sets the current directory.
|
|
|
|
ENTRY: pUserData - The user initiating the request.
|
|
|
|
pszDir - The name of the directory to move to.
|
|
|
|
RETURNS: APIERR - NO_ERROR if successful, otherwise a Win32
|
|
error code.
|
|
|
|
HISTORY:
|
|
KeithMo 09-Mar-1993 Created.
|
|
KeithMo 23-Mar-1993 Added per-drive current directory support.
|
|
|
|
********************************************************************/
|
|
APIERR
|
|
VirtualChDir(
|
|
USER_DATA * pUserData,
|
|
CHAR * pszDir
|
|
)
|
|
{
|
|
CHAR chDrive;
|
|
CHAR * pszCurrent;
|
|
INT iDrive;
|
|
APIERR err = NO_ERROR;
|
|
CHAR szCanonDir[MAX_PATH];
|
|
|
|
FTPD_ASSERT( pUserData != NULL );
|
|
|
|
//
|
|
// Canonicalize the new path.
|
|
//
|
|
|
|
err = VirtualCanonicalize( pUserData,
|
|
szCanonDir,
|
|
pszDir,
|
|
ReadAccess );
|
|
|
|
if( err != NO_ERROR )
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "cannot canonicalize %s - %s, error %lu\n",
|
|
pUserData->szDir,
|
|
pszDir,
|
|
err ));
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
//
|
|
// Validate the drive letter.
|
|
//
|
|
|
|
chDrive = szCanonDir[0];
|
|
|
|
if( ( chDrive >= 'a' ) && ( chDrive <= 'z' ) )
|
|
{
|
|
chDrive -= ( 'a' - 'A' );
|
|
}
|
|
|
|
iDrive = (INT)( chDrive - 'A' );
|
|
|
|
if( ( iDrive < 0 ) || ( iDrive >= 26 ) )
|
|
{
|
|
FTPD_PRINT(( "%c is an invalid drive letter\n",
|
|
chDrive ));
|
|
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Try to open the directory.
|
|
//
|
|
|
|
pszCurrent = pUserData->apszDirs[iDrive];
|
|
|
|
if( ( pszCurrent == NULL ) || ( strcmp( pszCurrent, szCanonDir ) != 0 ) )
|
|
{
|
|
HANDLE hDir;
|
|
|
|
err = OpenDosPath( &hDir,
|
|
szCanonDir,
|
|
SYNCHRONIZE | FILE_TRAVERSE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT );
|
|
|
|
if( err == NO_ERROR )
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "opened directory %s, handle = %08lX\n",
|
|
szCanonDir,
|
|
hDir ));
|
|
}
|
|
|
|
//
|
|
// Directory successfully opened. Save the handle
|
|
// in the per-user data.
|
|
//
|
|
|
|
if( pUserData->hDir != INVALID_HANDLE_VALUE )
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "closing directory handle %08lX\n",
|
|
pUserData->hDir ));
|
|
}
|
|
|
|
NtClose( pUserData->hDir );
|
|
}
|
|
|
|
pUserData->hDir = hDir;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Cannot open current directory.
|
|
//
|
|
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "cannot open directory %s, error %lu\n",
|
|
szCanonDir,
|
|
err ));
|
|
}
|
|
|
|
return err;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Update our per-drive current directory table. If
|
|
// we don't have a directory buffer for the current
|
|
// drive, then allocate one now.
|
|
//
|
|
|
|
if( pszCurrent == NULL )
|
|
{
|
|
pszCurrent = (CHAR *)FTPD_ALLOC( MAX_PATH );
|
|
|
|
if( pszCurrent == NULL )
|
|
{
|
|
err = GetLastError();
|
|
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "cannot chdir %s, error %lu\n",
|
|
szCanonDir,
|
|
err ));
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
pUserData->apszDirs[iDrive] = pszCurrent;
|
|
}
|
|
|
|
strcpy( pUserData->szDir, szCanonDir );
|
|
strcpy( pszCurrent, szCanonDir );
|
|
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "chdir to %s\n", szCanonDir ));
|
|
}
|
|
|
|
return NO_ERROR;
|
|
|
|
} // VirtualChDir
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: VirtualRmDir
|
|
|
|
SYNOPSIS: Removes an existing directory.
|
|
|
|
ENTRY: pUserData - The user initiating the request.
|
|
|
|
pszDir - The name of the directory to remove.
|
|
|
|
RETURNS: APIERR - NO_ERROR if successful, otherwise a Win32
|
|
error code.
|
|
|
|
HISTORY:
|
|
KeithMo 09-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
APIERR
|
|
VirtualRmDir(
|
|
USER_DATA * pUserData,
|
|
CHAR * pszDir
|
|
)
|
|
{
|
|
APIERR err;
|
|
CHAR szCanonDir[MAX_PATH];
|
|
|
|
err = VirtualCanonicalize( pUserData,
|
|
szCanonDir,
|
|
pszDir,
|
|
DeleteAccess );
|
|
|
|
if( err == NO_ERROR )
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "rmdir %s\n", szCanonDir ));
|
|
}
|
|
|
|
if( !RemoveDirectory( szCanonDir ) )
|
|
{
|
|
err = GetLastError();
|
|
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "cannot rmdir %s, error %lu\n",
|
|
szCanonDir,
|
|
err ));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "cannot canonicalize %s - %s, error %lu\n",
|
|
pUserData->szDir,
|
|
pszDir,
|
|
err ));
|
|
}
|
|
}
|
|
|
|
return err;
|
|
|
|
} // VirtualRmDir
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: VirtualMkDir
|
|
|
|
SYNOPSIS: Creates a new directory.
|
|
|
|
ENTRY: pUserData - The user initiating the request.
|
|
|
|
pszDir - The name of the directory to create.
|
|
|
|
RETURNS: APIERR - NO_ERROR if successful, otherwise a Win32
|
|
error code.
|
|
|
|
HISTORY:
|
|
KeithMo 09-Mar-1993 Created.
|
|
|
|
********************************************************************/
|
|
APIERR
|
|
VirtualMkDir(
|
|
USER_DATA * pUserData,
|
|
CHAR * pszDir
|
|
)
|
|
{
|
|
APIERR err;
|
|
CHAR szCanonDir[MAX_PATH];
|
|
|
|
FTPD_ASSERT( pUserData != NULL );
|
|
|
|
err = VirtualCanonicalize( pUserData,
|
|
szCanonDir,
|
|
pszDir,
|
|
CreateAccess );
|
|
|
|
if( err == NO_ERROR )
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "mkdir %s\n", szCanonDir ));
|
|
}
|
|
|
|
if( !CreateDirectory( szCanonDir, NULL ) )
|
|
{
|
|
err = GetLastError();
|
|
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "cannot mkdir %s, error %lu\n",
|
|
szCanonDir,
|
|
err ));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IF_DEBUG( VIRTUAL_IO )
|
|
{
|
|
FTPD_PRINT(( "cannot canonicalize %s - %s, error %lu\n",
|
|
pUserData->szDir,
|
|
pszDir,
|
|
err ));
|
|
}
|
|
}
|
|
|
|
return err;
|
|
|
|
} // VirtualMkDir
|
|
|
|
|
|
//
|
|
// Private functions.
|
|
//
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: VirtualpLogFileAccess
|
|
|
|
SYNOPSIS: If file access logging is enabled, then log this
|
|
access.
|
|
|
|
ENTRY: pUserData - The user initiating the request.
|
|
|
|
pszAction - Describes the action taken (open, create,
|
|
or append).
|
|
|
|
pszPath - The canonicalized path.
|
|
|
|
HISTORY:
|
|
KeithMo 11-Feb-1994 Created.
|
|
|
|
********************************************************************/
|
|
VOID
|
|
VirtualpLogFileAccess(
|
|
USER_DATA * pUserData,
|
|
CHAR * pszAction,
|
|
CHAR * pszPath
|
|
)
|
|
{
|
|
FTPD_ASSERT( pUserData != NULL );
|
|
FTPD_ASSERT( pszAction != NULL );
|
|
FTPD_ASSERT( pszPath != NULL );
|
|
|
|
if( nLogFileAccess == FTPD_LOG_DISABLED )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( nLogFileAccess == FTPD_LOG_DAILY )
|
|
{
|
|
SYSTEMTIME stNow;
|
|
|
|
//
|
|
// Determine if we need to open a new logfile.
|
|
//
|
|
|
|
//
|
|
// A few words about the following code. Since this function
|
|
// is called whenever a file is opened or created, it is
|
|
// important to avoid (when possible) grabbing the critical
|
|
// section that protects the log file. We really don't want
|
|
// hundreds of threads blocking on this critical section, just
|
|
// trying to determine if a new log file needs to be opened.
|
|
//
|
|
// There are two key features of the following code:
|
|
//
|
|
// 1. The current date is checked against the current
|
|
// log file's date outside the critical section. If
|
|
// the dates match, the current log file is used. If
|
|
// the dates don't match, the critical section is
|
|
// claimed and the dates are checked again. If they
|
|
// still don't match, a new log file is opened.
|
|
//
|
|
// 2. fileLog never points to a closed file stream. A
|
|
// new stream is opened before closing the old stream,
|
|
// thus the service is never without a valid stream.
|
|
//
|
|
|
|
GetLocalTime( &stNow );
|
|
|
|
if( ( stNow.wYear != stPrevious.wYear ) ||
|
|
( stNow.wMonth != stPrevious.wMonth ) ||
|
|
( stNow.wDay != stPrevious.wDay ) )
|
|
{
|
|
EnterCriticalSection( &csLogFileLock );
|
|
|
|
if( ( stNow.wYear != stPrevious.wYear ) ||
|
|
( stNow.wMonth != stPrevious.wMonth ) ||
|
|
( stNow.wDay != stPrevious.wDay ) )
|
|
{
|
|
FILE * fileTmp;
|
|
|
|
fileTmp = fileLog;
|
|
fileLog = OpenLogFile();
|
|
|
|
if( fileTmp != NULL )
|
|
{
|
|
fclose( fileTmp );
|
|
}
|
|
}
|
|
|
|
LeaveCriticalSection( &csLogFileLock );
|
|
}
|
|
}
|
|
|
|
if( fileLog != NULL )
|
|
{
|
|
time_t now;
|
|
INT i;
|
|
|
|
//
|
|
// And now a few words about the following bit of code. Since
|
|
// we're trying to eliminate excessive blocking on critical section
|
|
// objects, there's a chance that the following fprintf() may fail.
|
|
// Consider the following scenario involving threads A and B:
|
|
//
|
|
// A: Checks the date and decides to not reopen the log file.
|
|
// B: Checks the date and decides to reopen the log file.
|
|
// A: Pushes the args for fprintf(), but doesn't call it yet.
|
|
// B: Reopens the log file & closes the old file.
|
|
// A: Calls fprintf() which fails because the stream is closed.
|
|
//
|
|
// We get around this by retrying the call to fprintf(). If the
|
|
// call to fprintf() fails, then some other thread must have closed
|
|
// the stream out from under us. The other thread either still
|
|
// owns the critical section (above) or has recently released it.
|
|
// To ensure we get a consistent log file stream on the retry,
|
|
// we'll enter & leave the file log critical section. This will
|
|
// guarantee that we have a valid stream on the second fprintf()
|
|
// attempt.
|
|
//
|
|
|
|
for( i = 0 ; i < LOG_FILE_RETRIES ; i++ )
|
|
{
|
|
time( &now );
|
|
|
|
if( fprintf( fileLog,
|
|
"%s %s %s %s %s",
|
|
inet_ntoa( pUserData->inetHost ),
|
|
pUserData->szUser,
|
|
pszAction,
|
|
pszPath,
|
|
asctime( localtime( &now ) ) ) > 0 )
|
|
{
|
|
break;
|
|
}
|
|
|
|
EnterCriticalSection( &csLogFileLock );
|
|
LeaveCriticalSection( &csLogFileLock );
|
|
}
|
|
|
|
fflush( fileLog );
|
|
}
|
|
|
|
} // VirtualpLogFileAccess
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: VirtualpSanitizePath
|
|
|
|
SYNOPSIS: Sanitizes a path by removing bogus path elements.
|
|
|
|
As expected, "\.\" entries are simply removed, and
|
|
"\..\" entries are removed along with the previous
|
|
path element.
|
|
|
|
To maintain compatibility with NT's path semantics,
|
|
additional transformations are required. All forward
|
|
slashes "/" are converted to backward slashes "\", and
|
|
repeated backslashes (such as "\\\") are mapped to
|
|
single backslashes. Also, any trailing path elements
|
|
consisting solely of dots "\....." are removed.
|
|
|
|
Thus, the path "d:\foo\.\bar\..\tar\....\......" is
|
|
mapped to "d:\foo\tar".
|
|
|
|
A state table (see the StateTable global at the
|
|
beginning of this file) is used to perform most of
|
|
the transformations. The table's rows are indexed
|
|
by current state, and the columns are indexed by
|
|
the current character's "class" (either slash, dot,
|
|
NULL, or other). Each entry in the table consists
|
|
of the new state tagged with an action to perform.
|
|
See the ACTION_* constants for the valid action
|
|
codes.
|
|
|
|
After the FSA is finished with the path, we make one
|
|
additional pass through it to remove any trailing
|
|
backslash, and to remove any trailing path elements
|
|
consisting solely of dots.
|
|
|
|
ENTRY: pszPath - The path to sanitize. This path must
|
|
be an absolute path of the form "D:\dir\etc".
|
|
|
|
HISTORY:
|
|
KeithMo 07-Sep-1994 Created.
|
|
|
|
********************************************************************/
|
|
VOID
|
|
VirtualpSanitizePath(
|
|
CHAR * pszPath
|
|
)
|
|
{
|
|
CHAR * pszSrc;
|
|
CHAR * pszDest;
|
|
CHAR * pszHead;
|
|
CHAR ch;
|
|
INT State;
|
|
INT Class;
|
|
|
|
//
|
|
// Ensure we got a valid absolute path (something starting
|
|
// with "D:\".
|
|
//
|
|
|
|
FTPD_ASSERT( pszPath != NULL );
|
|
FTPD_ASSERT( pszPath[1] == ':' );
|
|
FTPD_ASSERT( IS_PATH_SEP( pszPath[2] ) );
|
|
|
|
//
|
|
// Start our scan at the first "\".
|
|
//
|
|
|
|
pszHead = pszSrc = pszDest = pszPath + 2;
|
|
|
|
//
|
|
// State 0 is the initial state.
|
|
//
|
|
|
|
State = 0;
|
|
|
|
//
|
|
// Loop until we enter state 4 (the final, accepting state).
|
|
//
|
|
|
|
while( State != 4 )
|
|
{
|
|
//
|
|
// Grab the next character from the path and compute its
|
|
// character class. While we're at it, map any forward
|
|
// slashes to backward slashes.
|
|
//
|
|
|
|
ch = *pszSrc++;
|
|
|
|
switch( ch )
|
|
{
|
|
case '/' :
|
|
ch = '\\';
|
|
/* fall through */
|
|
|
|
case '\\' :
|
|
Class = 0;
|
|
break;
|
|
|
|
case '.' :
|
|
Class = 1;
|
|
break;
|
|
|
|
case '\0' :
|
|
Class = 2;
|
|
break;
|
|
|
|
default :
|
|
Class = 3;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Advance to the next state.
|
|
//
|
|
|
|
State = StateTable[State][Class];
|
|
|
|
//
|
|
// Perform the action associated with the state.
|
|
//
|
|
|
|
switch( State & ACTION_MASK )
|
|
{
|
|
case ACTION_EMIT_DOT_DOT_CH :
|
|
*pszDest++ = '.';
|
|
/* fall through */
|
|
|
|
case ACTION_EMIT_DOT_CH :
|
|
*pszDest++ = '.';
|
|
/* fall through */
|
|
|
|
case ACTION_EMIT_CH :
|
|
*pszDest++ = ch;
|
|
/* fall through */
|
|
|
|
case ACTION_NOTHING :
|
|
break;
|
|
|
|
case ACTION_BACKUP :
|
|
if( pszDest > ( pszHead + 1 ) )
|
|
{
|
|
pszDest--;
|
|
FTPD_ASSERT( *pszDest == '\\' );
|
|
|
|
*pszDest = '\0';
|
|
pszDest = strrchr( pszPath, '\\' ) + 1;
|
|
}
|
|
|
|
*pszDest = '\0';
|
|
break;
|
|
|
|
default :
|
|
FTPD_ASSERT( !"Invalid action code in state table!" );
|
|
State = 4;
|
|
*pszDest++ = '\0';
|
|
break;
|
|
}
|
|
|
|
State &= ~ACTION_MASK;
|
|
}
|
|
|
|
//
|
|
// Remove any trailing slash.
|
|
//
|
|
|
|
pszDest -= 2;
|
|
|
|
if( ( strlen( pszPath ) > 3 ) && ( *pszDest == '\\' ) )
|
|
{
|
|
*pszDest = '\0';
|
|
}
|
|
|
|
//
|
|
// If the final path elements consists solely of dots, remove them.
|
|
//
|
|
|
|
while( strlen( pszPath ) > 3 )
|
|
{
|
|
pszDest = strrchr( pszPath, '\\' );
|
|
FTPD_ASSERT( pszDest != NULL );
|
|
|
|
pszHead = pszDest;
|
|
pszDest++;
|
|
|
|
while( ch = *pszDest++ )
|
|
{
|
|
if( ch != '.' )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( ch == '\0' )
|
|
{
|
|
if( pszHead == ( pszPath + 2 ) )
|
|
{
|
|
pszHead++;
|
|
}
|
|
|
|
*pszHead = '\0';
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
} // VirtualpSanitizePath
|
|
|