diff options
Diffstat (limited to 'source3/smbd/vfs.c')
-rw-r--r-- | source3/smbd/vfs.c | 968 |
1 files changed, 968 insertions, 0 deletions
diff --git a/source3/smbd/vfs.c b/source3/smbd/vfs.c new file mode 100644 index 0000000000..1e137dd908 --- /dev/null +++ b/source3/smbd/vfs.c @@ -0,0 +1,968 @@ +/* + Unix SMB/Netbios implementation. + Version 1.9. + VFS initialisation and support functions + Copyright (C) Tim Potter 1999 + Copyright (C) Alexander Bokovoy 2002 + Copyright (C) James Peach 2006 + + This program is free software; you can redistribute it and/or modify + 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/>. + + This work was sponsored by Optifacio Software Services, Inc. +*/ + +#include "includes.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +static_decl_vfs; + +struct vfs_init_function_entry { + char *name; + const vfs_op_tuple *vfs_op_tuples; + struct vfs_init_function_entry *prev, *next; +}; + +static struct vfs_init_function_entry *backends = NULL; + +/**************************************************************************** + maintain the list of available backends +****************************************************************************/ + +static struct vfs_init_function_entry *vfs_find_backend_entry(const char *name) +{ + struct vfs_init_function_entry *entry = backends; + + DEBUG(10, ("vfs_find_backend_entry called for %s\n", name)); + + while(entry) { + if (strcmp(entry->name, name)==0) return entry; + entry = entry->next; + } + + return NULL; +} + +NTSTATUS smb_register_vfs(int version, const char *name, const vfs_op_tuple *vfs_op_tuples) +{ + struct vfs_init_function_entry *entry = backends; + + if ((version != SMB_VFS_INTERFACE_VERSION)) { + DEBUG(0, ("Failed to register vfs module.\n" + "The module was compiled against SMB_VFS_INTERFACE_VERSION %d,\n" + "current SMB_VFS_INTERFACE_VERSION is %d.\n" + "Please recompile against the current Samba Version!\n", + version, SMB_VFS_INTERFACE_VERSION)); + return NT_STATUS_OBJECT_TYPE_MISMATCH; + } + + if (!name || !name[0] || !vfs_op_tuples) { + DEBUG(0,("smb_register_vfs() called with NULL pointer or empty name!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (vfs_find_backend_entry(name)) { + DEBUG(0,("VFS module %s already loaded!\n", name)); + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + entry = SMB_XMALLOC_P(struct vfs_init_function_entry); + entry->name = smb_xstrdup(name); + entry->vfs_op_tuples = vfs_op_tuples; + + DLIST_ADD(backends, entry); + DEBUG(5, ("Successfully added vfs backend '%s'\n", name)); + return NT_STATUS_OK; +} + +/**************************************************************************** + initialise default vfs hooks +****************************************************************************/ + +static void vfs_init_default(connection_struct *conn) +{ + DEBUG(3, ("Initialising default vfs hooks\n")); + vfs_init_custom(conn, DEFAULT_VFS_MODULE_NAME); +} + +/**************************************************************************** + initialise custom vfs hooks + ****************************************************************************/ + +static inline void vfs_set_operation(struct vfs_ops * vfs, vfs_op_type which, + struct vfs_handle_struct * handle, void * op) +{ + ((struct vfs_handle_struct **)&vfs->handles)[which] = handle; + ((void **)(void *)&vfs->ops)[which] = op; +} + +bool vfs_init_custom(connection_struct *conn, const char *vfs_object) +{ + const vfs_op_tuple *ops; + char *module_path = NULL; + char *module_name = NULL; + char *module_param = NULL, *p; + int i; + vfs_handle_struct *handle; + const struct vfs_init_function_entry *entry; + + if (!conn||!vfs_object||!vfs_object[0]) { + DEBUG(0,("vfs_init_custon() called with NULL pointer or emtpy vfs_object!\n")); + return False; + } + + if(!backends) { + static_init_vfs; + } + + DEBUG(3, ("Initialising custom vfs hooks from [%s]\n", vfs_object)); + + module_path = smb_xstrdup(vfs_object); + + p = strchr_m(module_path, ':'); + + if (p) { + *p = 0; + module_param = p+1; + trim_char(module_param, ' ', ' '); + } + + trim_char(module_path, ' ', ' '); + + module_name = smb_xstrdup(module_path); + + if ((module_name[0] == '/') && + (strcmp(module_path, DEFAULT_VFS_MODULE_NAME) != 0)) { + + /* + * Extract the module name from the path. Just use the base + * name of the last path component. + */ + + SAFE_FREE(module_name); + module_name = smb_xstrdup(strrchr_m(module_path, '/')+1); + + p = strchr_m(module_name, '.'); + + if (p != NULL) { + *p = '\0'; + } + } + + /* First, try to load the module with the new module system */ + if((entry = vfs_find_backend_entry(module_name)) || + (NT_STATUS_IS_OK(smb_probe_module("vfs", module_path)) && + (entry = vfs_find_backend_entry(module_name)))) { + + DEBUGADD(5,("Successfully loaded vfs module [%s] with the new modules system\n", vfs_object)); + + if ((ops = entry->vfs_op_tuples) == NULL) { + DEBUG(0, ("entry->vfs_op_tuples==NULL for [%s] failed\n", vfs_object)); + goto fail; + } + } else { + DEBUG(0,("Can't find a vfs module [%s]\n",vfs_object)); + goto fail; + } + + handle = TALLOC_ZERO_P(conn, vfs_handle_struct); + if (!handle) { + DEBUG(0,("TALLOC_ZERO() failed!\n")); + goto fail; + } + memcpy(&handle->vfs_next, &conn->vfs, sizeof(struct vfs_ops)); + handle->conn = conn; + if (module_param) { + handle->param = talloc_strdup(conn, module_param); + } + DLIST_ADD(conn->vfs_handles, handle); + + for(i=0; ops[i].op != NULL; i++) { + DEBUG(5, ("Checking operation #%d (type %d, layer %d)\n", i, ops[i].type, ops[i].layer)); + if(ops[i].layer == SMB_VFS_LAYER_OPAQUE) { + /* If this operation was already made opaque by different module, it + * will be overridden here. + */ + DEBUGADD(5, ("Making operation type %d opaque [module %s]\n", ops[i].type, vfs_object)); + vfs_set_operation(&conn->vfs_opaque, ops[i].type, handle, ops[i].op); + } + /* Change current VFS disposition*/ + DEBUGADD(5, ("Accepting operation type %d from module %s\n", ops[i].type, vfs_object)); + vfs_set_operation(&conn->vfs, ops[i].type, handle, ops[i].op); + } + + SAFE_FREE(module_path); + SAFE_FREE(module_name); + return True; + + fail: + SAFE_FREE(module_path); + SAFE_FREE(module_name); + return False; +} + +/***************************************************************** + Allow VFS modules to extend files_struct with VFS-specific state. + This will be ok for small numbers of extensions, but might need to + be refactored if it becomes more widely used. +******************************************************************/ + +#define EXT_DATA_AREA(e) ((uint8 *)(e) + sizeof(struct vfs_fsp_data)) + +void *vfs_add_fsp_extension_notype(vfs_handle_struct *handle, files_struct *fsp, size_t ext_size) +{ + struct vfs_fsp_data *ext; + void * ext_data; + + /* Prevent VFS modules adding multiple extensions. */ + if ((ext_data = vfs_fetch_fsp_extension(handle, fsp))) { + return ext_data; + } + + ext = (struct vfs_fsp_data *)TALLOC_ZERO( + handle->conn, sizeof(struct vfs_fsp_data) + ext_size); + if (ext == NULL) { + return NULL; + } + + ext->owner = handle; + ext->next = fsp->vfs_extension; + fsp->vfs_extension = ext; + return EXT_DATA_AREA(ext); +} + +void vfs_remove_fsp_extension(vfs_handle_struct *handle, files_struct *fsp) +{ + struct vfs_fsp_data *curr; + struct vfs_fsp_data *prev; + + for (curr = fsp->vfs_extension, prev = NULL; + curr; + prev = curr, curr = curr->next) { + if (curr->owner == handle) { + if (prev) { + prev->next = curr->next; + } else { + fsp->vfs_extension = curr->next; + } + TALLOC_FREE(curr); + return; + } + } +} + +void *vfs_memctx_fsp_extension(vfs_handle_struct *handle, files_struct *fsp) +{ + struct vfs_fsp_data *head; + + for (head = fsp->vfs_extension; head; head = head->next) { + if (head->owner == handle) { + return head; + } + } + + return NULL; +} + +void *vfs_fetch_fsp_extension(vfs_handle_struct *handle, files_struct *fsp) +{ + struct vfs_fsp_data *head; + + head = (struct vfs_fsp_data *)vfs_memctx_fsp_extension(handle, fsp); + if (head != NULL) { + return EXT_DATA_AREA(head); + } + + return NULL; +} + +#undef EXT_DATA_AREA + +/***************************************************************** + Generic VFS init. +******************************************************************/ + +bool smbd_vfs_init(connection_struct *conn) +{ + const char **vfs_objects; + unsigned int i = 0; + int j = 0; + + /* Normal share - initialise with disk access functions */ + vfs_init_default(conn); + vfs_objects = lp_vfs_objects(SNUM(conn)); + + /* Override VFS functions if 'vfs object' was not specified*/ + if (!vfs_objects || !vfs_objects[0]) + return True; + + for (i=0; vfs_objects[i] ;) { + i++; + } + + for (j=i-1; j >= 0; j--) { + if (!vfs_init_custom(conn, vfs_objects[j])) { + DEBUG(0, ("smbd_vfs_init: vfs_init_custom failed for %s\n", vfs_objects[j])); + return False; + } + } + return True; +} + +/******************************************************************* + Check if directory exists. +********************************************************************/ + +bool vfs_directory_exist(connection_struct *conn, const char *dname, SMB_STRUCT_STAT *st) +{ + SMB_STRUCT_STAT st2; + bool ret; + + if (!st) + st = &st2; + + if (SMB_VFS_STAT(conn,dname,st) != 0) + return(False); + + ret = S_ISDIR(st->st_mode); + if(!ret) + errno = ENOTDIR; + + return ret; +} + +/******************************************************************* + Check if an object exists in the vfs. +********************************************************************/ + +bool vfs_object_exist(connection_struct *conn,const char *fname,SMB_STRUCT_STAT *sbuf) +{ + SMB_STRUCT_STAT st; + + if (!sbuf) + sbuf = &st; + + ZERO_STRUCTP(sbuf); + + if (SMB_VFS_STAT(conn,fname,sbuf) == -1) + return(False); + return True; +} + +/******************************************************************* + Check if a file exists in the vfs. +********************************************************************/ + +bool vfs_file_exist(connection_struct *conn, const char *fname,SMB_STRUCT_STAT *sbuf) +{ + SMB_STRUCT_STAT st; + + if (!sbuf) + sbuf = &st; + + ZERO_STRUCTP(sbuf); + + if (SMB_VFS_STAT(conn,fname,sbuf) == -1) + return False; + return(S_ISREG(sbuf->st_mode)); +} + +/**************************************************************************** + Read data from fsp on the vfs. (note: EINTR re-read differs from vfs_write_data) +****************************************************************************/ + +ssize_t vfs_read_data(files_struct *fsp, char *buf, size_t byte_count) +{ + size_t total=0; + + while (total < byte_count) + { + ssize_t ret = SMB_VFS_READ(fsp, buf + total, + byte_count - total); + + if (ret == 0) return total; + if (ret == -1) { + if (errno == EINTR) + continue; + else + return -1; + } + total += ret; + } + return (ssize_t)total; +} + +ssize_t vfs_pread_data(files_struct *fsp, char *buf, + size_t byte_count, SMB_OFF_T offset) +{ + size_t total=0; + + while (total < byte_count) + { + ssize_t ret = SMB_VFS_PREAD(fsp, buf + total, + byte_count - total, offset + total); + + if (ret == 0) return total; + if (ret == -1) { + if (errno == EINTR) + continue; + else + return -1; + } + total += ret; + } + return (ssize_t)total; +} + +/**************************************************************************** + Write data to a fd on the vfs. +****************************************************************************/ + +ssize_t vfs_write_data(struct smb_request *req, + files_struct *fsp, + const char *buffer, + size_t N) +{ + size_t total=0; + ssize_t ret; + + if (req && req->unread_bytes) { + SMB_ASSERT(req->unread_bytes == N); + /* VFS_RECVFILE must drain the socket + * before returning. */ + req->unread_bytes = 0; + return SMB_VFS_RECVFILE(smbd_server_fd(), + fsp, + (SMB_OFF_T)-1, + N); + } + + while (total < N) { + ret = SMB_VFS_WRITE(fsp, buffer + total, N - total); + + if (ret == -1) + return -1; + if (ret == 0) + return total; + + total += ret; + } + return (ssize_t)total; +} + +ssize_t vfs_pwrite_data(struct smb_request *req, + files_struct *fsp, + const char *buffer, + size_t N, + SMB_OFF_T offset) +{ + size_t total=0; + ssize_t ret; + + if (req && req->unread_bytes) { + SMB_ASSERT(req->unread_bytes == N); + /* VFS_RECVFILE must drain the socket + * before returning. */ + req->unread_bytes = 0; + return SMB_VFS_RECVFILE(smbd_server_fd(), + fsp, + offset, + N); + } + + while (total < N) { + ret = SMB_VFS_PWRITE(fsp, buffer + total, N - total, + offset + total); + + if (ret == -1) + return -1; + if (ret == 0) + return total; + + total += ret; + } + return (ssize_t)total; +} +/**************************************************************************** + An allocate file space call using the vfs interface. + Allocates space for a file from a filedescriptor. + Returns 0 on success, -1 on failure. +****************************************************************************/ + +int vfs_allocate_file_space(files_struct *fsp, SMB_BIG_UINT len) +{ + int ret; + SMB_STRUCT_STAT st; + connection_struct *conn = fsp->conn; + SMB_BIG_UINT space_avail; + SMB_BIG_UINT bsize,dfree,dsize; + + release_level_2_oplocks_on_change(fsp); + + /* + * Actually try and commit the space on disk.... + */ + + DEBUG(10,("vfs_allocate_file_space: file %s, len %.0f\n", fsp->fsp_name, (double)len )); + + if (((SMB_OFF_T)len) < 0) { + DEBUG(0,("vfs_allocate_file_space: %s negative len requested.\n", fsp->fsp_name )); + errno = EINVAL; + return -1; + } + + ret = SMB_VFS_FSTAT(fsp, &st); + if (ret == -1) + return ret; + + if (len == (SMB_BIG_UINT)st.st_size) + return 0; + + if (len < (SMB_BIG_UINT)st.st_size) { + /* Shrink - use ftruncate. */ + + DEBUG(10,("vfs_allocate_file_space: file %s, shrink. Current size %.0f\n", + fsp->fsp_name, (double)st.st_size )); + + flush_write_cache(fsp, SIZECHANGE_FLUSH); + if ((ret = SMB_VFS_FTRUNCATE(fsp, (SMB_OFF_T)len)) != -1) { + set_filelen_write_cache(fsp, len); + } + return ret; + } + + /* Grow - we need to test if we have enough space. */ + + if (!lp_strict_allocate(SNUM(fsp->conn))) + return 0; + + len -= st.st_size; + len /= 1024; /* Len is now number of 1k blocks needed. */ + space_avail = get_dfree_info(conn,fsp->fsp_name,False,&bsize,&dfree,&dsize); + if (space_avail == (SMB_BIG_UINT)-1) { + return -1; + } + + DEBUG(10,("vfs_allocate_file_space: file %s, grow. Current size %.0f, needed blocks = %.0f, space avail = %.0f\n", + fsp->fsp_name, (double)st.st_size, (double)len, (double)space_avail )); + + if (len > space_avail) { + errno = ENOSPC; + return -1; + } + + return 0; +} + +/**************************************************************************** + A vfs set_filelen call. + set the length of a file from a filedescriptor. + Returns 0 on success, -1 on failure. +****************************************************************************/ + +int vfs_set_filelen(files_struct *fsp, SMB_OFF_T len) +{ + int ret; + + release_level_2_oplocks_on_change(fsp); + DEBUG(10,("vfs_set_filelen: ftruncate %s to len %.0f\n", fsp->fsp_name, (double)len)); + flush_write_cache(fsp, SIZECHANGE_FLUSH); + if ((ret = SMB_VFS_FTRUNCATE(fsp, len)) != -1) { + set_filelen_write_cache(fsp, len); + notify_fname(fsp->conn, NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_SIZE + | FILE_NOTIFY_CHANGE_ATTRIBUTES, + fsp->fsp_name); + } + + return ret; +} + +/**************************************************************************** + A vfs fill sparse call. + Writes zeros from the end of file to len, if len is greater than EOF. + Used only by strict_sync. + Returns 0 on success, -1 on failure. +****************************************************************************/ + +static char *sparse_buf; +#define SPARSE_BUF_WRITE_SIZE (32*1024) + +int vfs_fill_sparse(files_struct *fsp, SMB_OFF_T len) +{ + int ret; + SMB_STRUCT_STAT st; + SMB_OFF_T offset; + size_t total; + size_t num_to_write; + ssize_t pwrite_ret; + + release_level_2_oplocks_on_change(fsp); + ret = SMB_VFS_FSTAT(fsp, &st); + if (ret == -1) { + return ret; + } + + if (len <= st.st_size) { + return 0; + } + + DEBUG(10,("vfs_fill_sparse: write zeros in file %s from len %.0f to len %.0f (%.0f bytes)\n", + fsp->fsp_name, (double)st.st_size, (double)len, (double)(len - st.st_size))); + + flush_write_cache(fsp, SIZECHANGE_FLUSH); + + if (!sparse_buf) { + sparse_buf = SMB_CALLOC_ARRAY(char, SPARSE_BUF_WRITE_SIZE); + if (!sparse_buf) { + errno = ENOMEM; + return -1; + } + } + + offset = st.st_size; + num_to_write = len - st.st_size; + total = 0; + + while (total < num_to_write) { + size_t curr_write_size = MIN(SPARSE_BUF_WRITE_SIZE, (num_to_write - total)); + + pwrite_ret = SMB_VFS_PWRITE(fsp, sparse_buf, curr_write_size, offset + total); + if (pwrite_ret == -1) { + DEBUG(10,("vfs_fill_sparse: SMB_VFS_PWRITE for file %s failed with error %s\n", + fsp->fsp_name, strerror(errno) )); + return -1; + } + if (pwrite_ret == 0) { + return 0; + } + + total += pwrite_ret; + } + + set_filelen_write_cache(fsp, len); + return 0; +} + +/**************************************************************************** + Transfer some data (n bytes) between two file_struct's. +****************************************************************************/ + +static ssize_t vfs_read_fn(void *file, void *buf, size_t len) +{ + struct files_struct *fsp = (struct files_struct *)file; + + return SMB_VFS_READ(fsp, buf, len); +} + +static ssize_t vfs_write_fn(void *file, const void *buf, size_t len) +{ + struct files_struct *fsp = (struct files_struct *)file; + + return SMB_VFS_WRITE(fsp, buf, len); +} + +SMB_OFF_T vfs_transfer_file(files_struct *in, files_struct *out, SMB_OFF_T n) +{ + return transfer_file_internal((void *)in, (void *)out, n, + vfs_read_fn, vfs_write_fn); +} + +/******************************************************************* + A vfs_readdir wrapper which just returns the file name. +********************************************************************/ + +char *vfs_readdirname(connection_struct *conn, void *p) +{ + SMB_STRUCT_DIRENT *ptr= NULL; + char *dname; + + if (!p) + return(NULL); + + ptr = SMB_VFS_READDIR(conn, (DIR *)p); + if (!ptr) + return(NULL); + + dname = ptr->d_name; + +#ifdef NEXT2 + if (telldir(p) < 0) + return(NULL); +#endif + +#ifdef HAVE_BROKEN_READDIR_NAME + /* using /usr/ucb/cc is BAD */ + dname = dname - 2; +#endif + + return(dname); +} + +/******************************************************************* + A wrapper for vfs_chdir(). +********************************************************************/ + +int vfs_ChDir(connection_struct *conn, const char *path) +{ + int res; + static char *LastDir = NULL; + + if (!LastDir) { + LastDir = SMB_STRDUP(""); + } + + if (strcsequal(path,".")) + return(0); + + if (*path == '/' && strcsequal(LastDir,path)) + return(0); + + DEBUG(4,("vfs_ChDir to %s\n",path)); + + res = SMB_VFS_CHDIR(conn,path); + if (!res) { + SAFE_FREE(LastDir); + LastDir = SMB_STRDUP(path); + } + return(res); +} + +/******************************************************************* + Return the absolute current directory path - given a UNIX pathname. + Note that this path is returned in DOS format, not UNIX + format. Note this can be called with conn == NULL. +********************************************************************/ + +struct getwd_cache_key { + SMB_DEV_T dev; + SMB_INO_T ino; +}; + +char *vfs_GetWd(TALLOC_CTX *ctx, connection_struct *conn) +{ + char s[PATH_MAX+1]; + SMB_STRUCT_STAT st, st2; + char *result; + DATA_BLOB cache_value; + struct getwd_cache_key key; + + *s = 0; + + if (!lp_getwd_cache()) { + goto nocache; + } + + SET_STAT_INVALID(st); + + if (SMB_VFS_STAT(conn, ".",&st) == -1) { + /* + * Known to fail for root: the directory may be NFS-mounted + * and exported with root_squash (so has no root access). + */ + DEBUG(1,("vfs_GetWd: couldn't stat \".\" error %s " + "(NFS problem ?)\n", strerror(errno) )); + goto nocache; + } + + ZERO_STRUCT(key); /* unlikely, but possible padding */ + key.dev = st.st_dev; + key.ino = st.st_ino; + + if (!memcache_lookup(smbd_memcache(), GETWD_CACHE, + data_blob_const(&key, sizeof(key)), + &cache_value)) { + goto nocache; + } + + SMB_ASSERT((cache_value.length > 0) + && (cache_value.data[cache_value.length-1] == '\0')); + + if ((SMB_VFS_STAT(conn, (char *)cache_value.data, &st2) == 0) + && (st.st_dev == st2.st_dev) && (st.st_ino == st2.st_ino) + && (S_ISDIR(st.st_mode))) { + /* + * Ok, we're done + */ + result = talloc_strdup(ctx, (char *)cache_value.data); + if (result == NULL) { + errno = ENOMEM; + } + return result; + } + + nocache: + + /* + * We don't have the information to hand so rely on traditional + * methods. The very slow getcwd, which spawns a process on some + * systems, or the not quite so bad getwd. + */ + + if (!SMB_VFS_GETWD(conn,s)) { + DEBUG(0, ("vfs_GetWd: SMB_VFS_GETWD call failed: %s\n", + strerror(errno))); + return NULL; + } + + if (lp_getwd_cache() && VALID_STAT(st)) { + ZERO_STRUCT(key); /* unlikely, but possible padding */ + key.dev = st.st_dev; + key.ino = st.st_ino; + + memcache_add(smbd_memcache(), GETWD_CACHE, + data_blob_const(&key, sizeof(key)), + data_blob_const(s, strlen(s)+1)); + } + + result = talloc_strdup(ctx, s); + if (result == NULL) { + errno = ENOMEM; + } + return result; +} + +/******************************************************************* + Reduce a file name, removing .. elements and checking that + it is below dir in the heirachy. This uses realpath. +********************************************************************/ + +NTSTATUS check_reduced_name(connection_struct *conn, const char *fname) +{ +#ifdef REALPATH_TAKES_NULL + bool free_resolved_name = True; +#else + char resolved_name_buf[PATH_MAX+1]; + bool free_resolved_name = False; +#endif + char *resolved_name = NULL; + size_t con_path_len = strlen(conn->connectpath); + char *p = NULL; + + DEBUG(3,("reduce_name [%s] [%s]\n", fname, conn->connectpath)); + +#ifdef REALPATH_TAKES_NULL + resolved_name = SMB_VFS_REALPATH(conn,fname,NULL); +#else + resolved_name = SMB_VFS_REALPATH(conn,fname,resolved_name_buf); +#endif + + if (!resolved_name) { + switch (errno) { + case ENOTDIR: + DEBUG(3,("reduce_name: Component not a directory in getting realpath for %s\n", fname)); + return map_nt_error_from_unix(errno); + case ENOENT: + { + TALLOC_CTX *ctx = talloc_tos(); + char *tmp_fname = NULL; + char *last_component = NULL; + /* Last component didn't exist. Remove it and try and canonicalise the directory. */ + + tmp_fname = talloc_strdup(ctx, fname); + if (!tmp_fname) { + return NT_STATUS_NO_MEMORY; + } + p = strrchr_m(tmp_fname, '/'); + if (p) { + *p++ = '\0'; + last_component = p; + } else { + last_component = tmp_fname; + tmp_fname = talloc_strdup(ctx, + "."); + if (!tmp_fname) { + return NT_STATUS_NO_MEMORY; + } + } + +#ifdef REALPATH_TAKES_NULL + resolved_name = SMB_VFS_REALPATH(conn,tmp_fname,NULL); +#else + resolved_name = SMB_VFS_REALPATH(conn,tmp_fname,resolved_name_buf); +#endif + if (!resolved_name) { + DEBUG(3,("reduce_name: couldn't get realpath for %s\n", fname)); + return map_nt_error_from_unix(errno); + } + tmp_fname = talloc_asprintf(ctx, + "%s/%s", + resolved_name, + last_component); + if (!tmp_fname) { + return NT_STATUS_NO_MEMORY; + } +#ifdef REALPATH_TAKES_NULL + SAFE_FREE(resolved_name); + resolved_name = SMB_STRDUP(tmp_fname); + if (!resolved_name) { + DEBUG(0,("reduce_name: malloc fail for %s\n", tmp_fname)); + return NT_STATUS_NO_MEMORY; + } +#else + safe_strcpy(resolved_name_buf, tmp_fname, PATH_MAX); + resolved_name = resolved_name_buf; +#endif + break; + } + default: + DEBUG(1,("reduce_name: couldn't get realpath for %s\n", fname)); + return map_nt_error_from_unix(errno); + } + } + + DEBUG(10,("reduce_name realpath [%s] -> [%s]\n", fname, resolved_name)); + + if (*resolved_name != '/') { + DEBUG(0,("reduce_name: realpath doesn't return absolute paths !\n")); + if (free_resolved_name) { + SAFE_FREE(resolved_name); + } + return NT_STATUS_OBJECT_NAME_INVALID; + } + + /* Check for widelinks allowed. */ + if (!lp_widelinks(SNUM(conn)) && (strncmp(conn->connectpath, resolved_name, con_path_len) != 0)) { + DEBUG(2, ("reduce_name: Bad access attempt: %s is a symlink outside the share path", fname)); + if (free_resolved_name) { + SAFE_FREE(resolved_name); + } + return NT_STATUS_ACCESS_DENIED; + } + + /* Check if we are allowing users to follow symlinks */ + /* Patch from David Clerc <David.Clerc@cui.unige.ch> + University of Geneva */ + +#ifdef S_ISLNK + if (!lp_symlinks(SNUM(conn))) { + SMB_STRUCT_STAT statbuf; + if ( (SMB_VFS_LSTAT(conn,fname,&statbuf) != -1) && + (S_ISLNK(statbuf.st_mode)) ) { + if (free_resolved_name) { + SAFE_FREE(resolved_name); + } + DEBUG(3,("reduce_name: denied: file path name %s is a symlink\n",resolved_name)); + return NT_STATUS_ACCESS_DENIED; + } + } +#endif + + DEBUG(3,("reduce_name: %s reduced to %s\n", fname, resolved_name)); + if (free_resolved_name) { + SAFE_FREE(resolved_name); + } + return NT_STATUS_OK; +} |