FreeRDP/channels/drive/client/drive_file.c
Norbert Federa e393499ff8 rdpdr/drive: fixes for hung peers & info requests
drive_file_init: only allow directories and regular files
This is fixes issue #2071

Even if the server is only interested in getting a file's or directory's
attributes FreeRDP still tries to open the object with read permissions.
If the FreeRDP client has no read permissions for this object the call will
fail which forces the server to perform some expensive workarounds (e.g.
getting to the stat buffers via a directory enumeration request).

On Linux we can try to open the file or directory with the O_PATH flag in
order to obtain a file descriptor who's only purpose is to perform operations
that act purely at the file descriptor level.
2014-09-02 15:42:44 +02:00

762 lines
18 KiB
C

/**
* FreeRDP: A Remote Desktop Protocol Implementation
* File System Virtual Channel
*
* Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2010-2011 Vic Lee
* Copyright 2012 Gerald Richter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifndef _WIN32
#define __USE_LARGEFILE64
#define _LARGEFILE_SOURCE
#define _LARGEFILE64_SOURCE
#include <sys/time.h>
#endif
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <winpr/crt.h>
#include <winpr/file.h>
#include <winpr/stream.h>
#include <freerdp/channels/rdpdr.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_FCNTL_H
#define __USE_GNU /* for O_PATH */
#include <fcntl.h>
#undef __USE_GNU
#endif
#ifdef _WIN32
#pragma comment(lib, "Shlwapi.lib")
#include <Shlwapi.h>
#endif
#include "drive_file.h"
#ifdef _WIN32
#pragma warning(push)
#pragma warning(disable: 4244)
#endif
static void drive_file_fix_path(char* path)
{
int i;
int length;
length = (int) strlen(path);
for (i = 0; i < length; i++)
{
if (path[i] == '\\')
path[i] = '/';
}
#ifdef WIN32
if ((length == 3) && (path[1] == ':') && (path[2] == '/'))
return;
#else
if ((length == 1) && (path[0] == '/'))
return;
#endif
if ((length > 0) && (path[length - 1] == '/'))
path[length - 1] = '\0';
}
static char* drive_file_combine_fullpath(const char* base_path, const char* path)
{
char* fullpath;
fullpath = (char*) malloc(strlen(base_path) + strlen(path) + 1);
strcpy(fullpath, base_path);
strcat(fullpath, path);
drive_file_fix_path(fullpath);
return fullpath;
}
static BOOL drive_file_remove_dir(const char* path)
{
DIR* dir;
char* p;
struct STAT st;
struct dirent* pdirent;
BOOL ret = TRUE;
dir = opendir(path);
if (dir == NULL)
return FALSE;
pdirent = readdir(dir);
while (pdirent)
{
if (strcmp(pdirent->d_name, ".") == 0 || strcmp(pdirent->d_name, "..") == 0)
{
pdirent = readdir(dir);
continue;
}
p = (char*) malloc(strlen(path) + strlen(pdirent->d_name) + 2);
sprintf(p, "%s/%s", path, pdirent->d_name);
if (STAT(p, &st) != 0)
{
ret = FALSE;
}
else if (S_ISDIR(st.st_mode))
{
ret = drive_file_remove_dir(p);
}
else if (unlink(p) < 0)
{
ret = FALSE;
}
else
{
ret = TRUE;
}
free(p);
if (!ret)
break;
pdirent = readdir(dir);
}
closedir(dir);
if (ret)
{
if (rmdir(path) < 0)
{
ret = FALSE;
}
}
return ret;
}
static void drive_file_set_fullpath(DRIVE_FILE* file, char* fullpath)
{
free(file->fullpath);
file->fullpath = fullpath;
file->filename = strrchr(file->fullpath, '/');
if (file->filename == NULL)
file->filename = file->fullpath;
else
file->filename += 1;
}
static BOOL drive_file_init(DRIVE_FILE* file, UINT32 DesiredAccess, UINT32 CreateDisposition, UINT32 CreateOptions)
{
struct STAT st;
BOOL exists;
#ifdef WIN32
const static int mode = _S_IREAD | _S_IWRITE ;
#else
const static int mode = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH;
BOOL largeFile = FALSE;
#endif
int oflag = 0;
if (STAT(file->fullpath, &st) == 0)
{
file->is_dir = (S_ISDIR(st.st_mode) ? TRUE : FALSE);
if (!file->is_dir && !S_ISREG(st.st_mode))
{
file->err = EPERM;
return TRUE;
}
#ifndef WIN32
if (st.st_size > (unsigned long) 0x07FFFFFFF)
largeFile = TRUE;
#endif
exists = TRUE;
}
else
{
file->is_dir = ((CreateOptions & FILE_DIRECTORY_FILE) ? TRUE : FALSE);
if (file->is_dir)
{
/* Should only create the directory if the disposition allows for it */
if ((CreateDisposition == FILE_OPEN_IF) || (CreateDisposition == FILE_CREATE))
{
if (mkdir(file->fullpath, mode) != 0)
{
file->err = errno;
return TRUE;
}
}
}
exists = FALSE;
}
if (file->is_dir)
{
file->dir = opendir(file->fullpath);
if (file->dir == NULL)
{
file->err = errno;
return TRUE;
}
}
else
{
switch (CreateDisposition)
{
case FILE_SUPERSEDE:
oflag = O_TRUNC | O_CREAT;
break;
case FILE_OPEN:
break;
case FILE_CREATE:
oflag = O_CREAT | O_EXCL;
break;
case FILE_OPEN_IF:
oflag = O_CREAT;
break;
case FILE_OVERWRITE:
oflag = O_TRUNC;
break;
case FILE_OVERWRITE_IF:
oflag = O_TRUNC | O_CREAT;
break;
default:
break;
}
if ((CreateOptions & FILE_DELETE_ON_CLOSE) && (DesiredAccess & DELETE))
{
file->delete_pending = TRUE;
}
if ((DesiredAccess & GENERIC_ALL)
|| (DesiredAccess & GENERIC_WRITE)
|| (DesiredAccess & FILE_WRITE_DATA)
|| (DesiredAccess & FILE_APPEND_DATA))
{
oflag |= O_RDWR;
}
else
{
oflag |= O_RDONLY;
}
#ifndef WIN32
if (largeFile)
{
oflag |= O_LARGEFILE;
}
#else
oflag |= O_BINARY;
#endif
file->fd = OPEN(file->fullpath, oflag, mode);
if (file->fd == -1)
{
file->err = errno;
return TRUE;
}
}
return TRUE;
}
DRIVE_FILE* drive_file_new(const char* base_path, const char* path, UINT32 id,
UINT32 DesiredAccess, UINT32 CreateDisposition, UINT32 CreateOptions)
{
DRIVE_FILE* file;
file = (DRIVE_FILE*) malloc(sizeof(DRIVE_FILE));
ZeroMemory(file, sizeof(DRIVE_FILE));
file->id = id;
file->basepath = (char*) base_path;
drive_file_set_fullpath(file, drive_file_combine_fullpath(base_path, path));
file->fd = -1;
if (!drive_file_init(file, DesiredAccess, CreateDisposition, CreateOptions))
{
drive_file_free(file);
return NULL;
}
#if defined(__linux__) && defined(O_PATH)
if (file->fd < 0 && file->err == EACCES)
{
/**
* We have no access permissions for the file or directory but if the
* peer is only interested in reading the object's attributes we can try
* to obtain a file descriptor who's only purpose is to perform
* operations that act purely at the file descriptor level.
* See open(2)
**/
{
if ((file->fd = OPEN(file->fullpath, O_PATH)) >= 0)
{
file->err = 0;
}
}
}
#endif
return file;
}
void drive_file_free(DRIVE_FILE* file)
{
if (file->fd != -1)
close(file->fd);
if (file->dir != NULL)
closedir(file->dir);
if (file->delete_pending)
{
if (file->is_dir)
drive_file_remove_dir(file->fullpath);
else
unlink(file->fullpath);
}
free(file->pattern);
free(file->fullpath);
free(file);
}
BOOL drive_file_seek(DRIVE_FILE* file, UINT64 Offset)
{
if (file->is_dir || file->fd == -1)
return FALSE;
if (LSEEK(file->fd, Offset, SEEK_SET) == (off_t)-1)
return FALSE;
return TRUE;
}
BOOL drive_file_read(DRIVE_FILE* file, BYTE* buffer, UINT32* Length)
{
ssize_t r;
if (file->is_dir || file->fd == -1)
return FALSE;
r = read(file->fd, buffer, *Length);
if (r < 0)
return FALSE;
*Length = (UINT32) r;
return TRUE;
}
BOOL drive_file_write(DRIVE_FILE* file, BYTE* buffer, UINT32 Length)
{
ssize_t r;
if (file->is_dir || file->fd == -1)
return FALSE;
while (Length > 0)
{
r = write(file->fd, buffer, Length);
if (r == -1)
return FALSE;
Length -= r;
buffer += r;
}
return TRUE;
}
BOOL drive_file_query_information(DRIVE_FILE* file, UINT32 FsInformationClass, wStream* output)
{
struct STAT st;
if (STAT(file->fullpath, &st) != 0)
{
Stream_Write_UINT32(output, 0); /* Length */
return FALSE;
}
switch (FsInformationClass)
{
case FileBasicInformation:
/* http://msdn.microsoft.com/en-us/library/cc232094.aspx */
Stream_Write_UINT32(output, 36); /* Length */
Stream_EnsureRemainingCapacity(output, 36);
Stream_Write_UINT64(output, FILE_TIME_SYSTEM_TO_RDP(st.st_mtime)); /* CreationTime */
Stream_Write_UINT64(output, FILE_TIME_SYSTEM_TO_RDP(st.st_atime)); /* LastAccessTime */
Stream_Write_UINT64(output, FILE_TIME_SYSTEM_TO_RDP(st.st_mtime)); /* LastWriteTime */
Stream_Write_UINT64(output, FILE_TIME_SYSTEM_TO_RDP(st.st_ctime)); /* ChangeTime */
Stream_Write_UINT32(output, FILE_ATTR_SYSTEM_TO_RDP(file, st)); /* FileAttributes */
/* Reserved(4), MUST NOT be added! */
break;
case FileStandardInformation:
/* http://msdn.microsoft.com/en-us/library/cc232088.aspx */
Stream_Write_UINT32(output, 22); /* Length */
Stream_EnsureRemainingCapacity(output, 22);
Stream_Write_UINT64(output, st.st_size); /* AllocationSize */
Stream_Write_UINT64(output, st.st_size); /* EndOfFile */
Stream_Write_UINT32(output, st.st_nlink); /* NumberOfLinks */
Stream_Write_UINT8(output, file->delete_pending ? 1 : 0); /* DeletePending */
Stream_Write_UINT8(output, file->is_dir ? 1 : 0); /* Directory */
/* Reserved(2), MUST NOT be added! */
break;
case FileAttributeTagInformation:
/* http://msdn.microsoft.com/en-us/library/cc232093.aspx */
Stream_Write_UINT32(output, 8); /* Length */
Stream_EnsureRemainingCapacity(output, 8);
Stream_Write_UINT32(output, FILE_ATTR_SYSTEM_TO_RDP(file, st)); /* FileAttributes */
Stream_Write_UINT32(output, 0); /* ReparseTag */
break;
default:
Stream_Write_UINT32(output, 0); /* Length */
return FALSE;
}
return TRUE;
}
int dir_empty(const char *path)
{
#ifdef _WIN32
return PathIsDirectoryEmptyA(path);
#else
struct dirent *dp;
int empty = 1;
DIR *dir = opendir(path);
if (dir == NULL) //Not a directory or doesn't exist
return 1;
while ((dp = readdir(dir)) != NULL) {
if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
continue; /* Skip . and .. */
empty = 0;
break;
}
closedir(dir);
return empty;
#endif
}
BOOL drive_file_set_information(DRIVE_FILE* file, UINT32 FsInformationClass, UINT32 Length, wStream* input)
{
char* s = NULL;
mode_t m;
UINT64 size;
int status;
char* fullpath;
struct STAT st;
#if defined(__linux__) && !defined(ANDROID)
struct timespec tv[2];
#else
struct timeval tv[2];
#endif
UINT64 LastWriteTime;
UINT32 FileAttributes;
UINT32 FileNameLength;
m = 0;
switch (FsInformationClass)
{
case FileBasicInformation:
/* http://msdn.microsoft.com/en-us/library/cc232094.aspx */
Stream_Seek_UINT64(input); /* CreationTime */
Stream_Seek_UINT64(input); /* LastAccessTime */
Stream_Read_UINT64(input, LastWriteTime);
Stream_Seek_UINT64(input); /* ChangeTime */
Stream_Read_UINT32(input, FileAttributes);
if (FSTAT(file->fd, &st) != 0)
return FALSE;
tv[0].tv_sec = st.st_atime;
tv[1].tv_sec = (LastWriteTime > 0 ? FILE_TIME_RDP_TO_SYSTEM(LastWriteTime) : st.st_mtime);
#ifndef WIN32
/* TODO on win32 */
#ifdef ANDROID
tv[0].tv_usec = 0;
tv[1].tv_usec = 0;
utimes(file->fullpath, tv);
#elif defined (__linux__)
tv[0].tv_nsec = 0;
tv[1].tv_nsec = 0;
futimens(file->fd, tv);
#else
tv[0].tv_usec = 0;
tv[1].tv_usec = 0;
futimes(file->fd, tv);
#endif
if (FileAttributes > 0)
{
m = st.st_mode;
if ((FileAttributes & FILE_ATTRIBUTE_READONLY) == 0)
m |= S_IWUSR;
else
m &= ~S_IWUSR;
if (m != st.st_mode)
fchmod(file->fd, st.st_mode);
}
#endif
break;
case FileEndOfFileInformation:
/* http://msdn.microsoft.com/en-us/library/cc232067.aspx */
case FileAllocationInformation:
/* http://msdn.microsoft.com/en-us/library/cc232076.aspx */
Stream_Read_UINT64(input, size);
#ifndef _WIN32
if (ftruncate(file->fd, size) != 0)
return FALSE;
#endif
break;
case FileDispositionInformation:
/* http://msdn.microsoft.com/en-us/library/cc232098.aspx */
/* http://msdn.microsoft.com/en-us/library/cc241371.aspx */
if (file->is_dir && !dir_empty(file->fullpath))
break;
if (Length)
Stream_Read_UINT8(input, file->delete_pending);
else
file->delete_pending = 1;
break;
case FileRenameInformation:
/* http://msdn.microsoft.com/en-us/library/cc232085.aspx */
Stream_Seek_UINT8(input); /* ReplaceIfExists */
Stream_Seek_UINT8(input); /* RootDirectory */
Stream_Read_UINT32(input, FileNameLength);
status = ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) Stream_Pointer(input),
FileNameLength / 2, &s, 0, NULL, NULL);
if (status < 1)
s = (char*) calloc(1, 1);
fullpath = drive_file_combine_fullpath(file->basepath, s);
free(s);
#ifdef _WIN32
if (file->fd)
close(file->fd);
#endif
if (rename(file->fullpath, fullpath) == 0)
{
drive_file_set_fullpath(file, fullpath);
#ifdef _WIN32
file->fd = OPEN(fullpath, O_RDWR | O_BINARY);
#endif
}
else
{
free(fullpath);
return FALSE;
}
break;
default:
return FALSE;
}
return TRUE;
}
BOOL drive_file_query_directory(DRIVE_FILE* file, UINT32 FsInformationClass, BYTE InitialQuery,
const char* path, wStream* output)
{
int length;
BOOL ret;
WCHAR* ent_path;
struct STAT st;
struct dirent* ent;
if (!file->dir)
{
Stream_Write_UINT32(output, 0); /* Length */
Stream_Write_UINT8(output, 0); /* Padding */
return FALSE;
}
if (InitialQuery != 0)
{
rewinddir(file->dir);
free(file->pattern);
if (path[0])
file->pattern = _strdup(strrchr(path, '\\') + 1);
else
file->pattern = NULL;
}
if (file->pattern)
{
do
{
ent = readdir(file->dir);
if (ent == NULL)
continue;
if (FilePatternMatchA(ent->d_name, file->pattern))
break;
}
while (ent);
}
else
{
ent = readdir(file->dir);
}
if (!ent)
{
Stream_Write_UINT32(output, 0); /* Length */
Stream_Write_UINT8(output, 0); /* Padding */
return FALSE;
}
memset(&st, 0, sizeof(struct STAT));
ent_path = (WCHAR*) malloc(strlen(file->fullpath) + strlen(ent->d_name) + 2);
sprintf((char*) ent_path, "%s/%s", file->fullpath, ent->d_name);
if (STAT((char*) ent_path, &st) != 0)
{
}
free(ent_path);
ent_path = NULL;
length = ConvertToUnicode(sys_code_page, 0, ent->d_name, -1, &ent_path, 0) * 2;
ret = TRUE;
switch (FsInformationClass)
{
case FileDirectoryInformation:
/* http://msdn.microsoft.com/en-us/library/cc232097.aspx */
Stream_Write_UINT32(output, 64 + length); /* Length */
Stream_EnsureRemainingCapacity(output, 64 + length);
Stream_Write_UINT32(output, 0); /* NextEntryOffset */
Stream_Write_UINT32(output, 0); /* FileIndex */
Stream_Write_UINT64(output, FILE_TIME_SYSTEM_TO_RDP(st.st_mtime)); /* CreationTime */
Stream_Write_UINT64(output, FILE_TIME_SYSTEM_TO_RDP(st.st_atime)); /* LastAccessTime */
Stream_Write_UINT64(output, FILE_TIME_SYSTEM_TO_RDP(st.st_mtime)); /* LastWriteTime */
Stream_Write_UINT64(output, FILE_TIME_SYSTEM_TO_RDP(st.st_ctime)); /* ChangeTime */
Stream_Write_UINT64(output, st.st_size); /* EndOfFile */
Stream_Write_UINT64(output, st.st_size); /* AllocationSize */
Stream_Write_UINT32(output, FILE_ATTR_SYSTEM_TO_RDP(file, st)); /* FileAttributes */
Stream_Write_UINT32(output, length); /* FileNameLength */
Stream_Write(output, ent_path, length);
break;
case FileFullDirectoryInformation:
/* http://msdn.microsoft.com/en-us/library/cc232068.aspx */
Stream_Write_UINT32(output, 68 + length); /* Length */
Stream_EnsureRemainingCapacity(output, 68 + length);
Stream_Write_UINT32(output, 0); /* NextEntryOffset */
Stream_Write_UINT32(output, 0); /* FileIndex */
Stream_Write_UINT64(output, FILE_TIME_SYSTEM_TO_RDP(st.st_mtime)); /* CreationTime */
Stream_Write_UINT64(output, FILE_TIME_SYSTEM_TO_RDP(st.st_atime)); /* LastAccessTime */
Stream_Write_UINT64(output, FILE_TIME_SYSTEM_TO_RDP(st.st_mtime)); /* LastWriteTime */
Stream_Write_UINT64(output, FILE_TIME_SYSTEM_TO_RDP(st.st_ctime)); /* ChangeTime */
Stream_Write_UINT64(output, st.st_size); /* EndOfFile */
Stream_Write_UINT64(output, st.st_size); /* AllocationSize */
Stream_Write_UINT32(output, FILE_ATTR_SYSTEM_TO_RDP(file, st)); /* FileAttributes */
Stream_Write_UINT32(output, length); /* FileNameLength */
Stream_Write_UINT32(output, 0); /* EaSize */
Stream_Write(output, ent_path, length);
break;
case FileBothDirectoryInformation:
/* http://msdn.microsoft.com/en-us/library/cc232095.aspx */
Stream_Write_UINT32(output, 93 + length); /* Length */
Stream_EnsureRemainingCapacity(output, 93 + length);
Stream_Write_UINT32(output, 0); /* NextEntryOffset */
Stream_Write_UINT32(output, 0); /* FileIndex */
Stream_Write_UINT64(output, FILE_TIME_SYSTEM_TO_RDP(st.st_mtime)); /* CreationTime */
Stream_Write_UINT64(output, FILE_TIME_SYSTEM_TO_RDP(st.st_atime)); /* LastAccessTime */
Stream_Write_UINT64(output, FILE_TIME_SYSTEM_TO_RDP(st.st_mtime)); /* LastWriteTime */
Stream_Write_UINT64(output, FILE_TIME_SYSTEM_TO_RDP(st.st_ctime)); /* ChangeTime */
Stream_Write_UINT64(output, st.st_size); /* EndOfFile */
Stream_Write_UINT64(output, st.st_size); /* AllocationSize */
Stream_Write_UINT32(output, FILE_ATTR_SYSTEM_TO_RDP(file, st)); /* FileAttributes */
Stream_Write_UINT32(output, length); /* FileNameLength */
Stream_Write_UINT32(output, 0); /* EaSize */
Stream_Write_UINT8(output, 0); /* ShortNameLength */
/* Reserved(1), MUST NOT be added! */
Stream_Zero(output, 24); /* ShortName */
Stream_Write(output, ent_path, length);
break;
case FileNamesInformation:
/* http://msdn.microsoft.com/en-us/library/cc232077.aspx */
Stream_Write_UINT32(output, 12 + length); /* Length */
Stream_EnsureRemainingCapacity(output, 12 + length);
Stream_Write_UINT32(output, 0); /* NextEntryOffset */
Stream_Write_UINT32(output, 0); /* FileIndex */
Stream_Write_UINT32(output, length); /* FileNameLength */
Stream_Write(output, ent_path, length);
break;
default:
Stream_Write_UINT32(output, 0); /* Length */
Stream_Write_UINT8(output, 0); /* Padding */
ret = FALSE;
break;
}
free(ent_path);
return ret;
}
#ifdef _WIN32
#pragma warning(pop)
#endif