/**********************************************************************/ /** 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 #include // // 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