/*
- Unix SMB/Netbios implementation.
- Version 1.9.
+ Unix SMB/CIFS implementation.
filename handling routines
Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 1999-2004
+ Copyright (C) Ying Chen 2000
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
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
-#include "includes.h"
+/*
+ * New hash table stat cache code added by Ying Chen.
+ */
-extern int DEBUGLEVEL;
-extern BOOL case_sensitive;
-extern BOOL case_preserve;
-extern BOOL short_case_preserve;
-extern fstring remote_machine;
-extern BOOL use_mangled_map;
+#include "includes.h"
-static BOOL scan_directory(char *path, char *name,connection_struct *conn,BOOL docache);
+static BOOL scan_directory(connection_struct *conn, const char *path, char *name,size_t maxlength);
/****************************************************************************
Check if two filenames are equal.
This needs to be careful about whether we are case sensitive.
****************************************************************************/
-static BOOL fname_equal(char *name1, char *name2)
-{
- int l1 = strlen(name1);
- int l2 = strlen(name2);
-
- /* handle filenames ending in a single dot */
- if (l1-l2 == 1 && name1[l1-1] == '.' && lp_strip_dot())
- {
- BOOL ret;
- name1[l1-1] = 0;
- ret = fname_equal(name1,name2);
- name1[l1-1] = '.';
- return(ret);
- }
-
- if (l2-l1 == 1 && name2[l2-1] == '.' && lp_strip_dot())
- {
- BOOL ret;
- name2[l2-1] = 0;
- ret = fname_equal(name1,name2);
- name2[l2-1] = '.';
- return(ret);
- }
-
- /* now normal filename handling */
- if (case_sensitive)
- return(strcmp(name1,name2) == 0);
-
- return(strequal(name1,name2));
-}
-
-/****************************************************************************
- Mangle the 2nd name and check if it is then equal to the first name.
-****************************************************************************/
-static BOOL mangled_equal(char *name1, char *name2)
+static BOOL fname_equal(const char *name1, const char *name2, BOOL case_sensitive)
{
- pstring tmpname;
-
- if (is_8_3(name2, True))
- return(False);
-
- pstrcpy(tmpname,name2);
- mangle_name_83(tmpname);
+ /* Normal filename handling */
+ if (case_sensitive)
+ return(strcmp(name1,name2) == 0);
- return(strequal(name1,tmpname));
+ return(strequal(name1,name2));
}
/****************************************************************************
- Stat cache code used in unix_convert.
-*****************************************************************************/
-
-static int global_stat_cache_lookups;
-static int global_stat_cache_misses;
-static int global_stat_cache_hits;
-
-/****************************************************************************
- Stat cache statistics code.
-*****************************************************************************/
-
-void print_stat_cache_statistics(void)
-{
- double eff = (100.0* (double)global_stat_cache_hits)/(double)global_stat_cache_lookups;
-
- DEBUG(0,("stat cache stats: lookups = %d, hits = %d, misses = %d, \
-stat cache was %f%% effective.\n", global_stat_cache_lookups,
- global_stat_cache_hits, global_stat_cache_misses, eff ));
-}
-
-typedef struct {
- ubi_dlNode link;
- int name_len;
- pstring orig_name;
- pstring translated_name;
-} stat_cache_entry;
-
-#define MAX_STAT_CACHE_SIZE 50
-
-static ubi_dlList stat_cache = { NULL, (ubi_dlNodePtr)&stat_cache, 0};
+ Mangle the 2nd name and check if it is then equal to the first name.
+****************************************************************************/
-/****************************************************************************
- Compare a pathname to a name in the stat cache - of a given length.
- Note - this code always checks that the next character in the pathname
- is either a '/' character, or a '\0' character - to ensure we only
- match *full* pathname components. Note we don't need to handle case
- here, if we're case insensitive the stat cache orig names are all upper
- case.
-*****************************************************************************/
-
-static BOOL stat_name_equal_len( char *stat_name, char *orig_name, int len)
+static BOOL mangled_equal(const char *name1, const char *name2,
+ const struct share_params *p)
{
- BOOL matched = (memcmp( stat_name, orig_name, len) == 0);
- if(orig_name[len] != '/' && orig_name[len] != '\0')
- return False;
-
- return matched;
+ pstring tmpname;
+
+ pstrcpy(tmpname, name2);
+ mangle_map(tmpname, True, False, p);
+ return strequal(name1, tmpname);
}
/****************************************************************************
- Add an entry into the stat cache.
-*****************************************************************************/
+ Cope with the differing wildcard and non-wildcard error cases.
+****************************************************************************/
-static void stat_cache_add( char *full_orig_name, char *orig_translated_path)
+static NTSTATUS determine_path_error(const char *name, BOOL allow_wcard_last_component)
{
- stat_cache_entry *scp;
- pstring orig_name;
- pstring translated_path;
- int namelen;
-
- if (!lp_stat_cache()) return;
-
- namelen = strlen(orig_translated_path);
-
- /*
- * Don't cache trivial valid directory entries.
- */
- if((*full_orig_name == '\0') || (strcmp(full_orig_name, ".") == 0) ||
- (strcmp(full_orig_name, "..") == 0))
- return;
-
- /*
- * If we are in case insentive mode, we need to
- * store names that need no translation - else, it
- * would be a waste.
- */
-
- if(case_sensitive && (strcmp(full_orig_name, orig_translated_path) == 0))
- return;
-
- /*
- * Remove any trailing '/' characters from the
- * translated path.
- */
-
- pstrcpy(translated_path, orig_translated_path);
- if(translated_path[namelen-1] == '/') {
- translated_path[namelen-1] = '\0';
- namelen--;
- }
-
- /*
- * We will only replace namelen characters
- * of full_orig_name.
- * StrnCpy always null terminates.
- */
-
- StrnCpy(orig_name, full_orig_name, namelen);
- if(!case_sensitive)
- strupper( orig_name );
-
- /*
- * Check this name doesn't exist in the cache before we
- * add it.
- */
-
- for( scp = (stat_cache_entry *)ubi_dlFirst( &stat_cache); scp;
- scp = (stat_cache_entry *)ubi_dlNext( scp )) {
- if((strcmp( scp->orig_name, orig_name) == 0) &&
- (strcmp( scp->translated_name, translated_path) == 0)) {
- /*
- * Name does exist - promote it.
- */
- if( (stat_cache_entry *)ubi_dlFirst( &stat_cache) != scp ) {
- ubi_dlRemThis( &stat_cache, scp);
- ubi_dlAddHead( &stat_cache, scp);
- }
- return;
- }
- }
-
- if((scp = (stat_cache_entry *)malloc(sizeof(stat_cache_entry))) == NULL) {
- DEBUG(0,("stat_cache_add: Out of memory !\n"));
- return;
- }
-
- pstrcpy(scp->orig_name, orig_name);
- pstrcpy(scp->translated_name, translated_path);
- scp->name_len = namelen;
-
- ubi_dlAddHead( &stat_cache, scp);
-
- DEBUG(10,("stat_cache_add: Added entry %s -> %s\n", scp->orig_name, scp->translated_name ));
-
- if(ubi_dlCount(&stat_cache) > lp_stat_cache_size()) {
- scp = (stat_cache_entry *)ubi_dlRemTail( &stat_cache );
- free((char *)scp);
- return;
- }
-}
+ const char *p;
-/****************************************************************************
- Look through the stat cache for an entry - promote it to the top if found.
- Return True if we translated (and did a scuccessful stat on) the entire name.
-*****************************************************************************/
+ if (!allow_wcard_last_component) {
+ /* Error code within a pathname. */
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
-static BOOL stat_cache_lookup( char *name, char *dirpath, char **start, SMB_STRUCT_STAT *pst)
-{
- stat_cache_entry *scp;
- stat_cache_entry *longest_hit = NULL;
- pstring chk_name;
- int namelen;
-
- if (!lp_stat_cache()) return False;
-
- namelen = strlen(name);
-
- *start = name;
- global_stat_cache_lookups++;
-
- /*
- * Don't lookup trivial valid directory entries.
- */
- if((*name == '\0') || (strcmp(name, ".") == 0) || (strcmp(name, "..") == 0)) {
- global_stat_cache_misses++;
- return False;
- }
-
- pstrcpy(chk_name, name);
- if(!case_sensitive)
- strupper( chk_name );
-
- for( scp = (stat_cache_entry *)ubi_dlFirst( &stat_cache); scp;
- scp = (stat_cache_entry *)ubi_dlNext( scp )) {
- if(scp->name_len <= namelen) {
- if(stat_name_equal_len(scp->orig_name, chk_name, scp->name_len)) {
- if((longest_hit == NULL) || (longest_hit->name_len <= scp->name_len))
- longest_hit = scp;
- }
- }
- }
-
- if(longest_hit == NULL) {
- DEBUG(10,("stat_cache_lookup: cache miss on %s\n", name));
- global_stat_cache_misses++;
- return False;
- }
-
- global_stat_cache_hits++;
-
- DEBUG(10,("stat_cache_lookup: cache hit for name %s. %s -> %s\n",
- name, longest_hit->orig_name, longest_hit->translated_name ));
-
- /*
- * longest_hit is the longest match we got in the list.
- * Check it exists - if so, overwrite the original name
- * and then promote it to the top.
- */
-
- if(dos_stat( longest_hit->translated_name, pst) != 0) {
- /*
- * Discard this entry.
- */
- ubi_dlRemThis( &stat_cache, longest_hit);
- free((char *)longest_hit);
- return False;
- }
-
- memcpy(name, longest_hit->translated_name, longest_hit->name_len);
- if( (stat_cache_entry *)ubi_dlFirst( &stat_cache) != longest_hit ) {
- ubi_dlRemThis( &stat_cache, longest_hit);
- ubi_dlAddHead( &stat_cache, longest_hit);
- }
-
- *start = &name[longest_hit->name_len];
- if(**start == '/')
- ++*start;
-
- StrnCpy( dirpath, longest_hit->translated_name, name - (*start));
-
- return (namelen == longest_hit->name_len);
+ /* We're terminating here so we
+ * can be a little slower and get
+ * the error code right. Windows
+ * treats the last part of the pathname
+ * separately I think, so if the last
+ * component is a wildcard then we treat
+ * this ./ as "end of component" */
+
+ p = strchr(name, '/');
+
+ if (!p && (ms_has_wild(name) || ISDOT(name))) {
+ /* Error code at the end of a pathname. */
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ } else {
+ /* Error code within a pathname. */
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
}
-
+
/****************************************************************************
This routine is called to convert names from the dos namespace to unix
namespace. It needs to handle any case conversions, mangling, format
We assume that we have already done a chdir() to the right "root" directory
for this service.
-The function will return False if some part of the name except for the last
-part cannot be resolved
+The function will return an NTSTATUS error if some part of the name except for the last
+part cannot be resolved, else NT_STATUS_OK.
+
+Note NT_STATUS_OK doesn't mean the name exists or is valid, just that we didn't
+get any fatal errors that should immediately terminate the calling
+SMB processing whilst resolving.
If the saved_last_component != 0, then the unmodified last component
of the pathname is returned there. This is used in an exceptional
case in reply_mv (so far). If saved_last_component == 0 then nothing
is returned there.
-The bad_path arg is set to True if the filename walk failed. This is
-used to pick the correct error code to return between ENOENT and ENOTDIR
-as Windows applications depend on ERRbadpath being returned if a component
-of a pathname does not exist.
+If last_component_wcard is true then a MS wildcard was detected and
+should be allowed in the last component of the path only.
+
+On exit from unix_convert, if *pst was not null, then the file stat
+struct will be returned if the file exists and was found, if not this
+stat struct will be filled with zeros (and this can be detected by checking
+for nlinks = 0, which can never be true for any file).
****************************************************************************/
-BOOL unix_convert(char *name,connection_struct *conn,char *saved_last_component,
- BOOL *bad_path, SMB_STRUCT_STAT *pst)
+NTSTATUS unix_convert(connection_struct *conn,
+ pstring name,
+ BOOL allow_wcard_last_component,
+ char *saved_last_component,
+ SMB_STRUCT_STAT *pst)
{
- SMB_STRUCT_STAT st;
- char *start, *end;
- pstring dirpath;
- pstring orig_path;
- int saved_errno;
- BOOL component_was_mangled = False;
- BOOL name_has_wildcard = False;
-#if 0
- /* Andrew's conservative code... JRA. */
- extern char magic_char;
-#endif
+ SMB_STRUCT_STAT st;
+ char *start, *end;
+ pstring dirpath;
+ pstring orig_path;
+ BOOL component_was_mangled = False;
+ BOOL name_has_wildcard = False;
+
+ SET_STAT_INVALID(*pst);
+
+ *dirpath = 0;
+
+ if(saved_last_component) {
+ *saved_last_component = 0;
+ }
+
+ if (conn->printer) {
+ /* we don't ever use the filenames on a printer share as a
+ filename - so don't convert them */
+ return NT_STATUS_OK;
+ }
- DEBUG(5, ("unix_convert called on file \"%s\"\n", name));
-
- *dirpath = 0;
- *bad_path = False;
- if(pst) {
- ZERO_STRUCTP(pst);
- }
-
- if(saved_last_component)
- *saved_last_component = 0;
-
- /*
- * Convert to basic unix format - removing \ chars and cleaning it up.
- */
-
- unix_format(name);
- unix_clean_name(name);
-
- /*
- * Names must be relative to the root of the service - trim any leading /.
- * also trim trailing /'s.
- */
-
- trim_string(name,"/","/");
-
- /*
- * If we trimmed down to a single '\0' character
- * then we should use the "." directory to avoid
- * searching the cache, but not if we are in a
- * printing share.
- */
-
- if (!*name && (!conn -> printer)) {
- name[0] = '.';
- name[1] = '\0';
- }
-
- /*
- * Ensure saved_last_component is valid even if file exists.
- */
-
- if(saved_last_component) {
- end = strrchr(name, '/');
- if(end)
- pstrcpy(saved_last_component, end + 1);
- else
- pstrcpy(saved_last_component, name);
- }
-
- if (!case_sensitive &&
- (!case_preserve || (is_8_3(name, False) && !short_case_preserve)))
- strnorm(name);
-
- /*
- * Check if it's a printer file.
- */
- if (conn->printer) {
- if ((! *name) || strchr(name,'/') || !is_8_3(name, True)) {
- char *s;
- fstring name2;
- slprintf(name2,sizeof(name2)-1,"%.6s.XXXXXX",remote_machine);
-
- /*
- * Sanitise the name.
- */
-
- for (s=name2 ; *s ; s++)
- if (!issafe(*s)) *s = '_';
- pstrcpy(name,(char *)mktemp(name2));
- }
- return(True);
- }
-
- /*
- * If we trimmed down to a single '\0' character
- * then we will be using the "." directory.
- * As we know this is valid we can return true here.
- */
-
- if(!*name)
- return(True);
-
- start = name;
- while (strncmp(start,"./",2) == 0)
- start += 2;
-
- pstrcpy(orig_path, name);
-
- if(stat_cache_lookup( name, dirpath, &start, &st)) {
- if(pst)
- *pst = st;
- return True;
- }
-
- /*
- * stat the name - if it exists then we are all done!
- */
-
- if (dos_stat(name,&st) == 0) {
- stat_cache_add(orig_path, name);
- DEBUG(5,("conversion finished %s -> %s\n",orig_path, name));
- if(pst)
- *pst = st;
- return(True);
- }
-
- saved_errno = errno;
-
- DEBUG(5,("unix_convert begin: name = %s, dirpath = %s, start = %s\n",
- name, dirpath, start));
-
- /*
- * A special case - if we don't have any mangling chars and are case
- * sensitive then searching won't help.
- */
-
- if (case_sensitive && !is_mangled(name) &&
- !lp_strip_dot() && !use_mangled_map && (saved_errno != ENOENT))
- return(False);
-
- if(strchr(start,'?') || strchr(start,'*'))
- name_has_wildcard = True;
-
- /*
- * is_mangled() was changed to look at an entire pathname, not
- * just a component. JRA.
- */
-
- if(is_mangled(start))
- component_was_mangled = True;
-
-#if 0
- /* Keep Andrew's conservative code around, just in case. JRA. */
- /* this is an extremely conservative test for mangled names. */
- if (strchr(start,magic_char))
- component_was_mangled = True;
+ DEBUG(5, ("unix_convert called on file \"%s\"\n", name));
+
+ /*
+ * Conversion to basic unix format is already done in check_path_syntax().
+ */
+
+ /*
+ * Names must be relative to the root of the service - any leading /.
+ * and trailing /'s should have been trimmed by check_path_syntax().
+ */
+
+#ifdef DEVELOPER
+ SMB_ASSERT(*name != '/');
#endif
- /*
- * Now we need to recursively match the name against the real
- * directory structure.
- */
-
- /*
- * Match each part of the path name separately, trying the names
- * as is first, then trying to scan the directory for matching names.
- */
-
- for (; start ; start = (end?end+1:(char *)NULL)) {
- /*
- * Pinpoint the end of this section of the filename.
- */
- end = strchr(start, '/');
-
- /*
- * Chop the name at this point.
- */
- if (end)
- *end = 0;
-
- if(saved_last_component != 0)
- pstrcpy(saved_last_component, end ? end + 1 : start);
-
- /*
- * Check if the name exists up to this point.
- */
- if (dos_stat(name, &st) == 0) {
- /*
- * It exists. it must either be a directory or this must be
- * the last part of the path for it to be OK.
- */
- if (end && !(st.st_mode & S_IFDIR)) {
- /*
- * An intermediate part of the name isn't a directory.
- */
- DEBUG(5,("Not a dir %s\n",start));
- *end = '/';
- return(False);
- }
-
- } else {
- pstring rest;
-
- *rest = 0;
-
- /*
- * Remember the rest of the pathname so it can be restored
- * later.
- */
-
- if (end)
- pstrcpy(rest,end+1);
-
- /*
- * Try to find this part of the path in the directory.
- */
-
- if (strchr(start,'?') || strchr(start,'*') ||
- !scan_directory(dirpath, start, conn, end?True:False)) {
- if (end) {
- /*
- * An intermediate part of the name can't be found.
- */
- DEBUG(5,("Intermediate not found %s\n",start));
- *end = '/';
-
- /*
- * We need to return the fact that the intermediate
- * name resolution failed. This is used to return an
- * error of ERRbadpath rather than ERRbadfile. Some
- * Windows applications depend on the difference between
- * these two errors.
- */
- *bad_path = True;
- return(False);
- }
+ /*
+ * If we trimmed down to a single '\0' character
+ * then we should use the "." directory to avoid
+ * searching the cache, but not if we are in a
+ * printing share.
+ * As we know this is valid we can return true here.
+ */
+
+ if (!*name) {
+ name[0] = '.';
+ name[1] = '\0';
+ if (SMB_VFS_STAT(conn,name,&st) == 0) {
+ *pst = st;
+ }
+ DEBUG(5,("conversion finished \"\" -> %s\n",name));
+ return NT_STATUS_OK;
+ }
+
+ if (name[0] == '.' && (name[1] == '/' || name[1] == '\0')) {
+ /* Start of pathname can't be "." only. */
+ if (name[1] == '\0' || name[2] == '\0') {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ } else {
+ return determine_path_error(&name[2], allow_wcard_last_component);
+ }
+ }
+
+ /*
+ * Ensure saved_last_component is valid even if file exists.
+ */
+
+ if(saved_last_component) {
+ end = strrchr_m(name, '/');
+ if (end) {
+ pstrcpy(saved_last_component, end + 1);
+ } else {
+ pstrcpy(saved_last_component, name);
+ }
+ }
+
+ /*
+ * Large directory fix normalization. If we're case sensitive, and
+ * the case preserving parameters are set to "no", normalize the case of
+ * the incoming filename from the client WHETHER IT EXISTS OR NOT !
+ * This is in conflict with the current (3.0.20) man page, but is
+ * what people expect from the "large directory howto". I'll update
+ * the man page. Thanks to jht@samba.org for finding this. JRA.
+ */
+
+ if (conn->case_sensitive && !conn->case_preserve && !conn->short_case_preserve) {
+ strnorm(name, lp_defaultcase(SNUM(conn)));
+ }
+
+ start = name;
+ pstrcpy(orig_path, name);
+
+ if(!conn->case_sensitive && stat_cache_lookup(conn, name, dirpath, &start, &st)) {
+ *pst = st;
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * stat the name - if it exists then we are all done!
+ */
+
+ if (SMB_VFS_STAT(conn,name,&st) == 0) {
+ /* Ensure we catch all names with in "/."
+ this is disallowed under Windows. */
+ const char *p = strstr(name, "/."); /* mb safe. */
+ if (p) {
+ if (p[2] == '/') {
+ /* Error code within a pathname. */
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ } else if (p[2] == '\0') {
+ /* Error code at the end of a pathname. */
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ }
+ stat_cache_add(orig_path, name, conn->case_sensitive);
+ DEBUG(5,("conversion finished %s -> %s\n",orig_path, name));
+ *pst = st;
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(5,("unix_convert begin: name = %s, dirpath = %s, start = %s\n", name, dirpath, start));
+
+ /*
+ * A special case - if we don't have any mangling chars and are case
+ * sensitive then searching won't help.
+ */
+
+ if (conn->case_sensitive &&
+ !mangle_is_mangled(name, conn->params) &&
+ !*lp_mangled_map(conn->params)) {
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * is_mangled() was changed to look at an entire pathname, not
+ * just a component. JRA.
+ */
+
+ if (mangle_is_mangled(start, conn->params)) {
+ component_was_mangled = True;
+ }
+
+ /*
+ * Now we need to recursively match the name against the real
+ * directory structure.
+ */
+
+ /*
+ * Match each part of the path name separately, trying the names
+ * as is first, then trying to scan the directory for matching names.
+ */
+
+ for (; start ; start = (end?end+1:(char *)NULL)) {
+ /*
+ * Pinpoint the end of this section of the filename.
+ */
+ end = strchr(start, '/'); /* mb safe. '/' can't be in any encoded char. */
+
+ /*
+ * Chop the name at this point.
+ */
+ if (end) {
+ *end = 0;
+ }
+
+ if (saved_last_component != 0) {
+ pstrcpy(saved_last_component, end ? end + 1 : start);
+ }
+
+ /* The name cannot have a component of "." */
+
+ if (ISDOT(start)) {
+ if (!end) {
+ /* Error code at the end of a pathname. */
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ return determine_path_error(end+1, allow_wcard_last_component);
+ }
+
+ /* The name cannot have a wildcard if it's not
+ the last component. */
+
+ name_has_wildcard = ms_has_wild(start);
+
+ /* Wildcard not valid anywhere. */
+ if (name_has_wildcard && !allow_wcard_last_component) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+
+ /* Wildcards never valid within a pathname. */
+ if (name_has_wildcard && end) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+
+ /*
+ * Check if the name exists up to this point.
+ */
+
+ if (SMB_VFS_STAT(conn,name, &st) == 0) {
+ /*
+ * It exists. it must either be a directory or this must be
+ * the last part of the path for it to be OK.
+ */
+ if (end && !(st.st_mode & S_IFDIR)) {
+ /*
+ * An intermediate part of the name isn't a directory.
+ */
+ DEBUG(5,("Not a dir %s\n",start));
+ *end = '/';
+ /*
+ * We need to return the fact that the intermediate
+ * name resolution failed. This is used to return an
+ * error of ERRbadpath rather than ERRbadfile. Some
+ * Windows applications depend on the difference between
+ * these two errors.
+ */
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+
+ if (!end) {
+ /*
+ * We just scanned for, and found the end of the path.
+ * We must return the valid stat struct.
+ * JRA.
+ */
+
+ *pst = st;
+ }
+
+ } else {
+ pstring rest;
+
+ /* Stat failed - ensure we don't use it. */
+ SET_STAT_INVALID(st);
+ *rest = 0;
+
+ /*
+ * Remember the rest of the pathname so it can be restored
+ * later.
+ */
+
+ if (end) {
+ pstrcpy(rest,end+1);
+ }
+
+ /* Reset errno so we can detect directory open errors. */
+ errno = 0;
+
+ /*
+ * Try to find this part of the path in the directory.
+ */
+
+ if (name_has_wildcard ||
+ !scan_directory(conn, dirpath, start, sizeof(pstring) - 1 - (start - name))) {
+ if (end) {
+ /*
+ * An intermediate part of the name can't be found.
+ */
+ DEBUG(5,("Intermediate not found %s\n",start));
+ *end = '/';
+
+ /*
+ * We need to return the fact that the intermediate
+ * name resolution failed. This is used to return an
+ * error of ERRbadpath rather than ERRbadfile. Some
+ * Windows applications depend on the difference between
+ * these two errors.
+ */
+
+ /* ENOENT and ENOTDIR both map to NT_STATUS_OBJECT_PATH_NOT_FOUND
+ in the filename walk. */
+
+ if (errno == ENOENT || errno == ENOTDIR) {
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+ return map_nt_error_from_unix(errno);
+ }
- /*
- * Just the last part of the name doesn't exist.
- * We may need to strupper() or strlower() it in case
- * this conversion is being used for file creation
- * purposes. If the filename is of mixed case then
- * don't normalise it.
- */
-
- if (!case_preserve && (!strhasupper(start) || !strhaslower(start)))
- strnorm(start);
-
- /*
- * check on the mangled stack to see if we can recover the
- * base of the filename.
- */
-
- if (is_mangled(start)) {
- check_mangled_cache( start );
- }
-
- DEBUG(5,("New file %s\n",start));
- return(True);
- }
-
- /*
- * Restore the rest of the string.
- */
- if (end) {
- pstrcpy(start+strlen(start)+1,rest);
- end = start + strlen(start);
- }
- } /* end else */
-
- /*
- * Add to the dirpath that we have resolved so far.
- */
- if (*dirpath)
- pstrcat(dirpath,"/");
-
- pstrcat(dirpath,start);
-
- /*
- * Don't cache a name with mangled or wildcard components
- * as this can change the size.
- */
-
- if(!component_was_mangled && !name_has_wildcard)
- stat_cache_add(orig_path, dirpath);
-
- /*
- * Restore the / that we wiped out earlier.
- */
- if (end)
- *end = '/';
- }
+ /* ENOENT is the only valid error here. */
+ if (errno != ENOENT) {
+ /* ENOENT and ENOTDIR both map to NT_STATUS_OBJECT_PATH_NOT_FOUND
+ in the filename walk. */
+ if (errno == ENOTDIR) {
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+ return map_nt_error_from_unix(errno);
+ }
+
+ /*
+ * Just the last part of the name doesn't exist.
+ * We need to strupper() or strlower() it as
+ * this conversion may be used for file creation
+ * purposes. Fix inspired by Thomas Neumann <t.neumann@iku-ag.de>.
+ */
+ if (!conn->case_preserve ||
+ (mangle_is_8_3(start, False, conn->params) &&
+ !conn->short_case_preserve)) {
+ strnorm(start, lp_defaultcase(SNUM(conn)));
+ }
+
+ /*
+ * check on the mangled stack to see if we can recover the
+ * base of the filename.
+ */
+
+ if (mangle_is_mangled(start, conn->params)) {
+ mangle_check_cache( start, sizeof(pstring) - 1 - (start - name), conn->params);
+ }
+
+ DEBUG(5,("New file %s\n",start));
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Restore the rest of the string. If the string was mangled the size
+ * may have changed.
+ */
+ if (end) {
+ end = start + strlen(start);
+ if (!safe_strcat(start, "/", sizeof(pstring) - 1 - (start - name)) ||
+ !safe_strcat(start, rest, sizeof(pstring) - 1 - (start - name))) {
+ return map_nt_error_from_unix(ENAMETOOLONG);
+ }
+ *end = '\0';
+ } else {
+ /*
+ * We just scanned for, and found the end of the path.
+ * We must return a valid stat struct if it exists.
+ * JRA.
+ */
+
+ if (SMB_VFS_STAT(conn,name, &st) == 0) {
+ *pst = st;
+ } else {
+ SET_STAT_INVALID(st);
+ }
+ }
+ } /* end else */
+
+#ifdef DEVELOPER
+ if (VALID_STAT(st) && get_delete_on_close_flag(file_id_sbuf(&st))) {
+ return NT_STATUS_DELETE_PENDING;
+ }
+#endif
+
+ /*
+ * Add to the dirpath that we have resolved so far.
+ */
+ if (*dirpath) {
+ pstrcat(dirpath,"/");
+ }
+
+ pstrcat(dirpath,start);
+
+ /*
+ * Don't cache a name with mangled or wildcard components
+ * as this can change the size.
+ */
+
+ if(!component_was_mangled && !name_has_wildcard) {
+ stat_cache_add(orig_path, dirpath, conn->case_sensitive);
+ }
+
+ /*
+ * Restore the / that we wiped out earlier.
+ */
+ if (end) {
+ *end = '/';
+ }
+ }
- /*
- * Don't cache a name with mangled or wildcard components
- * as this can change the size.
- */
+ /*
+ * Don't cache a name with mangled or wildcard components
+ * as this can change the size.
+ */
- if(!component_was_mangled && !name_has_wildcard)
- stat_cache_add(orig_path, name);
+ if(!component_was_mangled && !name_has_wildcard) {
+ stat_cache_add(orig_path, name, conn->case_sensitive);
+ }
- /*
- * The name has been resolved.
- */
+ /*
+ * The name has been resolved.
+ */
- DEBUG(5,("conversion finished %s -> %s\n",orig_path, name));
- return(True);
+ DEBUG(5,("conversion finished %s -> %s\n",orig_path, name));
+ return NT_STATUS_OK;
}
-
/****************************************************************************
-check a filename - possibly caling reducename
-
-This is called by every routine before it allows an operation on a filename.
-It does any final confirmation necessary to ensure that the filename is
-a valid one for the user to access.
+ Check a filename - possibly caling reducename.
+ This is called by every routine before it allows an operation on a filename.
+ It does any final confirmation necessary to ensure that the filename is
+ a valid one for the user to access.
****************************************************************************/
-BOOL check_name(char *name,connection_struct *conn)
+
+NTSTATUS check_name(connection_struct *conn, const pstring name)
{
- BOOL ret;
-
- errno = 0;
-
- if (IS_VETO_PATH(conn, name)) {
- DEBUG(5,("file path name %s vetoed\n",name));
- return(0);
- }
-
- ret = reduce_name(name,conn->connectpath,lp_widelinks(SNUM(conn)));
-
- /* 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 ( (dos_lstat(name,&statbuf) != -1) &&
- (S_ISLNK(statbuf.st_mode)) )
- {
- DEBUG(3,("check_name: denied: file path name %s is a symlink\n",name));
- ret=0;
- }
- }
-#endif
+ if (IS_VETO_PATH(conn, name)) {
+ /* Is it not dot or dot dot. */
+ if (!((name[0] == '.') && (!name[1] || (name[1] == '.' && !name[2])))) {
+ DEBUG(5,("check_name: file path name %s vetoed\n",name));
+ return map_nt_error_from_unix(ENOENT);
+ }
+ }
- if (!ret)
- DEBUG(5,("check_name on %s failed\n",name));
+ if (!lp_widelinks(SNUM(conn)) || !lp_symlinks(SNUM(conn))) {
+ NTSTATUS status = reduce_name(conn,name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("check_name: name %s failed with %s\n",name, nt_errstr(status)));
+ return status;
+ }
+ }
- return(ret);
+ return NT_STATUS_OK;
}
-
/****************************************************************************
-scan a directory to find a filename, matching without case sensitivity
-
-If the name looks like a mangled name then try via the mangling functions
+ Scan a directory to find a filename, matching without case sensitivity.
+ If the name looks like a mangled name then try via the mangling functions
****************************************************************************/
-static BOOL scan_directory(char *path, char *name,connection_struct *conn,BOOL docache)
+
+static BOOL scan_directory(connection_struct *conn, const char *path, char *name, size_t maxlength)
{
- void *cur_dir;
- char *dname;
- BOOL mangled;
- pstring name2;
-
- mangled = is_mangled(name);
-
- /* handle null paths */
- if (*path == 0)
- path = ".";
-
- if (docache && (dname = DirCacheCheck(path,name,SNUM(conn)))) {
- pstrcpy(name, dname);
- return(True);
- }
-
- /*
- * The incoming name can be mangled, and if we de-mangle it
- * here it will not compare correctly against the filename (name2)
- * read from the directory and then mangled by the name_map_mangle()
- * call. We need to mangle both names or neither.
- * (JRA).
- */
- if (mangled)
- mangled = !check_mangled_cache( name );
-
- /* open the directory */
- if (!(cur_dir = OpenDir(conn, path, True)))
- {
- DEBUG(3,("scan dir didn't open dir [%s]\n",path));
- return(False);
- }
-
- /* now scan for matching names */
- while ((dname = ReadDirName(cur_dir)))
- {
- if (*dname == '.' &&
- (strequal(dname,".") || strequal(dname,"..")))
- continue;
-
- pstrcpy(name2,dname);
- if (!name_map_mangle(name2,False,SNUM(conn))) continue;
-
- if ((mangled && mangled_equal(name,name2))
- || fname_equal(name, name2))
- {
- /* we've found the file, change it's name and return */
- if (docache) DirCacheAdd(path,name,dname,SNUM(conn));
- pstrcpy(name, dname);
- CloseDir(cur_dir);
- return(True);
+ struct smb_Dir *cur_dir;
+ const char *dname;
+ BOOL mangled;
+ long curpos;
+
+ mangled = mangle_is_mangled(name, conn->params);
+
+ /* handle null paths */
+ if (*path == 0)
+ path = ".";
+
+ /*
+ * The incoming name can be mangled, and if we de-mangle it
+ * here it will not compare correctly against the filename (name2)
+ * read from the directory and then mangled by the mangle_map()
+ * call. We need to mangle both names or neither.
+ * (JRA).
+ *
+ * Fix for bug found by Dina Fine. If in case sensitive mode then
+ * the mangle cache is no good (3 letter extension could be wrong
+ * case - so don't demangle in this case - leave as mangled and
+ * allow the mangling of the directory entry read (which is done
+ * case insensitively) to match instead. This will lead to more
+ * false positive matches but we fail completely without it. JRA.
+ */
+
+ if (mangled && !conn->case_sensitive) {
+ mangled = !mangle_check_cache( name, maxlength, conn->params);
+ }
+
+ /* open the directory */
+ if (!(cur_dir = OpenDir(conn, path, NULL, 0))) {
+ DEBUG(3,("scan dir didn't open dir [%s]\n",path));
+ return(False);
+ }
+
+ /* now scan for matching names */
+ curpos = 0;
+ while ((dname = ReadDirName(cur_dir, &curpos))) {
+
+ /* Is it dot or dot dot. */
+ if ((dname[0] == '.') && (!dname[1] || (dname[1] == '.' && !dname[2]))) {
+ continue;
+ }
+
+ /*
+ * At this point dname is the unmangled name.
+ * name is either mangled or not, depending on the state of the "mangled"
+ * variable. JRA.
+ */
+
+ /*
+ * Check mangled name against mangled name, or unmangled name
+ * against unmangled name.
+ */
+
+ if ((mangled && mangled_equal(name,dname,conn->params)) || fname_equal(name, dname, conn->case_sensitive)) {
+ /* we've found the file, change it's name and return */
+ safe_strcpy(name, dname, maxlength);
+ CloseDir(cur_dir);
+ return(True);
+ }
}
- }
- CloseDir(cur_dir);
- return(False);
+ CloseDir(cur_dir);
+ errno = ENOENT;
+ return(False);
}