/* -*- c-basic-offset: 8 -*-
rdesktop: A Remote Desktop Protocol client.
Disk Redirection
Copyright (C) Jeroen Meijer <jeroen@oldambt7.com> 2003-2008
Copyright 2003-2011 Peter Astrand <astrand@cendio.se> for Cendio AB
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Oracle GPL Disclaimer: For the avoidance of doubt, except that if any license choice
* other than GPL or LGPL is available it will apply instead, Oracle elects to use only
* the General Public License version 2 (GPLv2) at this time for any software where
* a choice of GPL license versions is made available with the language indicating
* that GPLv2 or any later version may be used, or where a choice of which version
* of the GPL is applied is otherwise unspecified.
*/
#include "disk.h"
#include <unistd.h>
#include <fcntl.h> /* open, close */
#include <dirent.h> /* opendir, closedir, readdir */
#include <fnmatch.h>
#include <errno.h> /* errno */
#include <stdio.h>
#include <utime.h>
#include <time.h> /* ctime */
#else
#endif
/* TODO: Fix mntent-handling for solaris
#if (defined(HAVE_MNTENT_H) && defined(HAVE_SETMNTENT))
#include <mntent.h>
#define USE_SETMNTENT
#endif
#ifdef HAVE_SYS_VFS_H
#endif
#ifdef HAVE_SYS_STATVFS_H
#endif
#ifdef HAVE_SYS_STATFS_H
#endif
#ifdef HAVE_SYS_PARAM_H
#endif
#ifdef HAVE_SYS_MOUNT_H
#endif
#include "rdesktop.h"
#ifdef STAT_STATFS3_OSF1
#define USE_STATFS
#endif
#ifdef STAT_STATVFS
#define USE_STATVFS
#endif
#ifdef STAT_STATVFS64
#define USE_STATVFS
#endif
#define USE_STATFS
#endif
#ifdef STAT_STATFS4
#define USE_STATFS
#endif
#if ((defined(USE_STATFS) && defined(HAVE_STRUCT_STATFS_F_NAMEMAX)) || (defined(USE_STATVFS) && defined(HAVE_STRUCT_STATVFS_F_NAMEMAX)))
#endif
#if ((defined(USE_STATFS) && defined(HAVE_STRUCT_STATFS_F_NAMELEN)) || (defined(USE_STATVFS) && defined(HAVE_STRUCT_STATVFS_F_NAMELEN)))
#endif
#ifndef F_NAMELEN
#endif
/* Dummy statfs fallback */
#ifndef STATFS_T
struct dummy_statfs_t
{
long f_bfree;
long f_bsize;
long f_bavail;
long f_blocks;
int f_namelen;
int f_namemax;
};
static int
{
return 0;
}
#endif
extern RDPDR_DEVICE g_rdpdr_device[];
typedef struct
{
unsigned long serial;
} FsInfoType;
static time_t
{
return ret1;
return ret;
}
/* Convert seconds since 1970 to a filetime */
static void
{
unsigned long long ticks;
}
/* Convert seconds since 1970 back to filetime */
static time_t
{
unsigned long long ticks;
ticks /= 10000000;
ticks -= 11644473600LL;
return (val);
}
/* A wrapper for ftruncate which supports growing files, even if the
native ftruncate doesn't. This is needed on Linux FAT filesystems,
for example. */
static int
{
int ret;
static const char zero = 0;
/* Try the simple method first */
{
return ret;
}
/*
* Some kind of error. Perhaps we were trying to grow. Retry
* in a safe way.
*/
/* Get current position */
{
perror("lseek");
return -1;
}
/* Seek to new size */
{
perror("lseek");
return -1;
}
/* Write a zero */
{
perror("write");
return -1;
}
/* Truncate. This shouldn't fail. */
{
perror("ftruncate");
return -1;
}
/* Restore position */
{
perror("lseek");
return -1;
}
return 0;
}
/* Just like open(2), but if a open with O_EXCL fails, retry with
GUARDED semantics. This might be necessary because some filesystems
(such as NFS filesystems mounted from a unfsd server) doesn't
support O_EXCL. GUARDED semantics are subject to race conditions,
but we can live with that.
*/
static int
{
int ret;
{
/* Success, or not using O_EXCL */
return ret;
}
/* An error occured, and we are using O_EXCL. In case the FS
doesn't support O_EXCL, some kind of error will be
returned. Unfortunately, we don't know which one. Linux
2.6.8 seems to return 524, but I cannot find a documented
#define for this case. So, we'll return only on errors that
we know aren't related to O_EXCL. */
switch (errno)
{
case EACCES:
case EEXIST:
case EINTR:
case EISDIR:
case ELOOP:
case ENAMETOOLONG:
case ENOENT:
case ENOTDIR:
return ret;
}
/* Retry with GUARDED semantics */
{
/* File exists */
return -1;
}
else
{
}
}
/* Enumeration of devices from rdesktop.c */
/* returns numer of units found and initialized. */
/* optarg looks like ':h=/mnt/floppy,b=/mnt/usbdevice1' */
/* when it arrives to this function. */
int
{
char *pos2;
int count = 0;
/* skip the first colon */
optarg++;
{
count++;
(*id)++;
}
return count;
}
/* Opens or creates a file or directory */
static RD_NTSTATUS
{
handle = 0;
flags = 0;
#ifdef VBOX
#else
#endif
/* Protect against mailicous servers:
somelongpath/.. not allowed
somelongpath/../b not allowed
somelongpath/..b in principle ok, but currently not allowed
somelongpath/b.. ok
somelongpath/b..b ok
somelongpath/b../c ok
*/
{
return RD_STATUS_ACCESS_DENIED;
}
switch (create_disposition)
{
case CREATE_ALWAYS:
break;
case CREATE_NEW:
/* If the file already exists, then fail. */
break;
case OPEN_ALWAYS:
/* Create if not already exists. */
break;
case OPEN_EXISTING:
/* Default behaviour */
break;
case TRUNCATE_EXISTING:
/* If the file does not exist, then fail. */
break;
}
/*printf("Open: \"%s\" flags: %X, accessmask: %X sharemode: %X create disp: %X\n", path, flags_and_attributes, accessmask, sharemode, create_disposition); */
/* Get information about file and set that flag ourselfs */
{
return RD_STATUS_FILE_IS_A_DIRECTORY;
else
}
{
{
}
if (!dirp)
{
switch (errno)
{
case EACCES:
return RD_STATUS_ACCESS_DENIED;
case ENOENT:
return RD_STATUS_NO_SUCH_FILE;
default:
perror("opendir");
return RD_STATUS_NO_SUCH_FILE;
}
}
}
else
{
if (accessmask & GENERIC_ALL
{
}
{
}
else
{
}
if (handle == -1)
{
switch (errno)
{
case EISDIR:
return RD_STATUS_FILE_IS_A_DIRECTORY;
case EACCES:
return RD_STATUS_ACCESS_DENIED;
case ENOENT:
return RD_STATUS_NO_SUCH_FILE;
case EEXIST:
return RD_STATUS_OBJECT_NAME_COLLISION;
default:
perror("open");
return RD_STATUS_NO_SUCH_FILE;
}
}
/* all read and writes of files should be non blocking */
perror("fcntl");
}
if (handle >= MAX_OPEN_FILES)
{
error("Maximum number of open files (%s) reached. Increase MAX_OPEN_FILES!\n",
handle);
}
if (dirp)
else
return RD_STATUS_SUCCESS;
}
static RD_NTSTATUS
{
{
{
perror("closedir");
return RD_STATUS_INVALID_HANDLE;
}
if (pfinfo->delete_on_close)
{
return RD_STATUS_ACCESS_DENIED;
}
}
else
{
{
perror("close");
return RD_STATUS_INVALID_HANDLE;
}
if (pfinfo->delete_on_close)
{
return RD_STATUS_ACCESS_DENIED;
}
}
return RD_STATUS_SUCCESS;
}
static RD_NTSTATUS
{
int n;
#if 0
/* browsing dir ???? */
/* each request is 24 bytes */
{
*result = 0;
return STATUS_SUCCESS;
}
#endif
if (n < 0)
{
*result = 0;
switch (errno)
{
case EISDIR:
/* Implement 24 Byte directory read ??
with STATUS_NOT_IMPLEMENTED server doesn't read again */
/* return STATUS_FILE_IS_A_DIRECTORY; */
return RD_STATUS_NOT_IMPLEMENTED;
default:
perror("read");
return RD_STATUS_INVALID_PARAMETER;
}
}
*result = n;
return RD_STATUS_SUCCESS;
}
static RD_NTSTATUS
{
int n;
if (n < 0)
{
perror("write");
*result = 0;
switch (errno)
{
case ENOSPC:
return RD_STATUS_DISK_FULL;
default:
return RD_STATUS_ACCESS_DENIED;
}
}
*result = n;
return RD_STATUS_SUCCESS;
}
{
/* Get information about file */
{
perror("stat");
return RD_STATUS_ACCESS_DENIED;
}
/* Set file attributes */
file_attributes = 0;
if (!file_attributes)
/* Return requested data */
switch (info_class)
{
case FileBasicInformation:
&ft_low);
break;
case FileStandardInformation:
out_uint32_le(out, 0);
out_uint32_le(out, 0);
break;
case FileObjectIdInformation:
break;
default:
return RD_STATUS_INVALID_PARAMETER;
}
return RD_STATUS_SUCCESS;
}
{
int mode;
switch (info_class)
{
case FileBasicInformation:
/* CreationTime */
/* AccessTime */
/* WriteTime */
/* ChangeTime */
return RD_STATUS_ACCESS_DENIED;
if (access_time)
if (write_time || change_time)
else
if (mod_time)
{
#if WITH_DEBUG_RDP5
printf("FileBasicInformation access time %s",
printf("FileBasicInformation modification time %s",
#endif
return RD_STATUS_ACCESS_DENIED;
}
if (!file_attributes)
break; /* not valid */
else
mode &= 0777;
#if WITH_DEBUG_RDP5
#endif
return RD_STATUS_ACCESS_DENIED;
break;
case FileRenameInformation:
return RD_STATUS_INVALID_PARAMETER;
return RD_STATUS_INVALID_PARAMETER;
#ifdef VBOX
newname);
#else
newname);
#endif
{
perror("rename");
return RD_STATUS_ACCESS_DENIED;
}
break;
/* As far as I understand it, the correct
thing to do here is to *schedule* a delete,
so it will be deleted when the file is
closed. Subsequent
FileDispositionInformation requests with
DeleteFile set to FALSE should unschedule
the delete. See
/* FileDispositionInformation always sets delete_on_close to true.
"STREAM in" includes Length(4bytes) , Padding(24bytes) and SetBuffer(zero byte).
Length is always set to zero.
- 2.2.3.3.9 Server Drive Set Information Request
*/
if ((pfinfo->accessmask &
{
/* if file exists in directory , necessary to return RD_STATUS_DIRECTORY_NOT_EMPTY with win2008
- 2.2.3.3.9 Server Drive Set Information Request
- 2.2.3.4.9 Client Drive Set Information Response
- 2.4.11 FileDispositionInformation
- 4.3.2 Set Delete-on-close using FileDispositionInformation Information Class (IRP_MJ_SET_INFORMATION)
*/
{
{
{
return RD_STATUS_DIRECTORY_NOT_EMPTY;
}
}
}
}
break;
/* Fall through to FileEndOfFileInformation,
which uses ftrunc. This is like Samba with
"strict allocation = false", and means that
we won't detect out-of-quota errors, for
example. */
case FileEndOfFileInformation:
/* prevents start of writing if not enough space left on device */
return RD_STATUS_DISK_FULL;
{
return RD_STATUS_DISK_FULL;
}
break;
default:
return RD_STATUS_INVALID_PARAMETER;
}
return RD_STATUS_SUCCESS;
}
{
return RD_STATUS_INVALID_DEVICE_REQUEST;
if (status != RD_STATUS_PENDING)
return status;
{
/*printf("disk_check_notify found changed event\n"); */
}
return status;
}
{
/* printf("start disk_create_notify info_class %X\n", info_class); */
if (info_class & 0x1000)
{ /* ???? */
if (ret == RD_STATUS_PENDING)
return RD_STATUS_SUCCESS;
}
/* printf("disk_create_notify: num_entries %d\n", pfinfo->notify.num_entries); */
return ret;
}
static RD_NTSTATUS
{
char *fullname;
{
perror("NotifyInfo");
return RD_STATUS_ACCESS_DENIED;
}
p->num_entries = 0;
p->total_time = 0;
if (!dpr)
{
perror("NotifyInfo");
return RD_STATUS_ACCESS_DENIED;
}
{
continue;
p->num_entries++;
{
}
}
return RD_STATUS_PENDING;
}
static FsInfoType *
{
#ifdef USE_SETMNTENT
struct mntent *e;
#endif
/* initialize */
#ifdef USE_SETMNTENT
if (!fdfs)
return &info;
{
{
{
if (fd >= 0)
{
/*FAT*/
{
}
{
/* info.Serial = (buf[128]<<24)+(buf[127]<<16)+(buf[126]<<8)+buf[125]; */
}
}
}
}
}
#else
/* initialize */
#endif
return &info;
}
{
{
perror("statfs");
return RD_STATUS_ACCESS_DENIED;
}
switch (info_class)
{
case FileFsVolumeInformation:
break;
case FileFsSizeInformation:
break;
break;
break;
case FileFsLabelInformation:
case FileFsDeviceInformation:
case FileFsControlInformation:
case FileFsMaximumInformation:
default:
return RD_STATUS_INVALID_PARAMETER;
}
return RD_STATUS_SUCCESS;
}
{
file_attributes = 0;
switch (info_class)
{
case FileDirectoryInformation:
case FileNamesInformation:
/* If a search pattern is received, remember this pattern, and restart search */
{
}
/* find next dirent matching pattern */
return RD_STATUS_NO_MORE_FILES;
/* Get information for directory entry */
#ifdef VBOX
#else
#endif
{
switch (errno)
{
case ENOENT:
case ELOOP:
case EACCES:
/* These are non-fatal errors. */
break;
default:
/* Fatal error. By returning STATUS_NO_SUCH_FILE,
the directory list operation will be aborted */
return RD_STATUS_NO_SUCH_FILE;
}
}
if (!file_attributes)
/* Return requested information */
break;
default:
return RD_STATUS_INVALID_PARAMETER;
}
switch (info_class)
{
&ft_low);
/* this should be correct according to MS-FSCC specification
but it only works when commented out... */
break;
case FileDirectoryInformation:
&ft_low);
break;
&ft_low);
break;
case FileNamesInformation:
break;
default:
return RD_STATUS_INVALID_PARAMETER;
}
return RD_STATUS_SUCCESS;
}
static RD_NTSTATUS
{
#ifdef VBOX
return RD_STATUS_INVALID_PARAMETER;
#else
return RD_STATUS_INVALID_PARAMETER;
#endif
/* extract operation */
request >>= 2;
request &= 0xfff;
switch (request)
{
case 25: /* ? */
case 42: /* ? */
default:
return RD_STATUS_INVALID_PARAMETER;
}
return RD_STATUS_SUCCESS;
}
disk_device_control /* device_control */
};