r23784: use the GPLv3 boilerplate as recommended by the FSF and the license text
[tprouty/samba.git] / source3 / smbd / filename.c
index 1d9c5ef754e6a0733f6945ebe91ee3600c68d0f4..737ed5a89c93e9928a16787dd0e195d34339f981 100644 (file)
@@ -1,12 +1,13 @@
 /* 
-   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
-   the Free Software Foundation; either version 2 of the License, or
+   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,
    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, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-#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 pstring global_myname;
-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)
+
+static BOOL fname_equal(const char *name1, const char *name2, BOOL case_sensitive)
 {
-  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));
-}
+       /* 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 mangled_equal(const char *name1, const char *name2,
+                         const struct share_params *p)
 {
-  pstring tmpname;
+       pstring tmpname;
+       
+       pstrcpy(tmpname, name2);
+       mangle_map(tmpname, True, False, p);
+       return strequal(name1, tmpname);
+}
 
-  if (is_8_3(name2, True))
-    return(False);
+/****************************************************************************
+ Cope with the differing wildcard and non-wildcard error cases.
+****************************************************************************/
 
-  pstrcpy(tmpname,name2);
-  mangle_name_83(tmpname);
+static NTSTATUS determine_path_error(const char *name, BOOL allow_wcard_last_component)
+{
+       const char *p;
 
-  return(strequal(name1,tmpname));
-}
+       if (!allow_wcard_last_component) {
+               /* Error code within a pathname. */
+               return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+       }
 
+       /* 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;
+       }
+}
+       
 /****************************************************************************
- Stat cache code used in unix_convert.
-*****************************************************************************/
+This routine is called to convert names from the dos namespace to unix
+namespace. It needs to handle any case conversions, mangling, format
+changes etc.
 
-static int global_stat_cache_lookups;
-static int global_stat_cache_misses;
-static int global_stat_cache_hits;
+We assume that we have already done a chdir() to the right "root" directory
+for this service.
 
-/****************************************************************************
- Stat cache statistics code.
-*****************************************************************************/
+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.
 
-void print_stat_cache_statistics(void)
+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.
+
+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).
+****************************************************************************/
+
+NTSTATUS unix_convert(connection_struct *conn,
+                       pstring name,
+                       BOOL allow_wcard_last_component,
+                       char *saved_last_component, 
+                       SMB_STRUCT_STAT *pst)
 {
-  double eff = (100.0* (double)global_stat_cache_hits)/(double)global_stat_cache_lookups;
+       SMB_STRUCT_STAT st;
+       char *start, *end;
+       pstring dirpath;
+       pstring orig_path;
+       BOOL component_was_mangled = False;
+       BOOL name_has_wildcard = False;
 
-  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 ));
-}
+       SET_STAT_INVALID(*pst);
 
-typedef struct {
-  ubi_dlNode link;
-  int name_len;
-  pstring orig_name;
-  pstring translated_name;
-} stat_cache_entry;
+       *dirpath = 0;
 
-#define MAX_STAT_CACHE_SIZE 50
+       if(saved_last_component) {
+               *saved_last_component = 0;
+       }
 
-static ubi_dlList stat_cache = { NULL, (ubi_dlNodePtr)&stat_cache, 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;
+       }
 
-/****************************************************************************
- 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)
-{
-  BOOL matched = (memcmp( stat_name, orig_name, len) == 0);
-  if(orig_name[len] != '/' && orig_name[len] != '\0')
-    return False;
+       DEBUG(5, ("unix_convert called on file \"%s\"\n", name));
 
-  return matched;
-}
+       /* 
+        * Conversion to basic unix format is already done in check_path_syntax().
+        */
 
-/****************************************************************************
- Add an entry into the stat cache.
-*****************************************************************************/
+       /* 
+        * Names must be relative to the root of the service - any leading /.
+        * and trailing /'s should have been trimmed by check_path_syntax().
+        */
 
-static void stat_cache_add( char *full_orig_name, char *orig_translated_path)
-{
-  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;
-  }
-}
+#ifdef DEVELOPER
+       SMB_ASSERT(*name != '/');
+#endif
 
-/****************************************************************************
- 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 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;
+       }
 
-static BOOL stat_cache_lookup(struct connection_struct *conn, 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(conn->vfs_ops.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);
-}
+       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);
+               }
+       }
 
-/****************************************************************************
- this routine converts from the dos and dfs namespace to the unix namespace.
-****************************************************************************/
-BOOL unix_dfs_convert(char *name,connection_struct *conn,
-                               char *saved_last_component, 
-                               BOOL *bad_path, SMB_STRUCT_STAT *pst)
-{
-       pstring local_path;
+       /*
+        * Ensure saved_last_component is valid even if file exists.
+        */
 
-       DEBUG(10,("unix_dfs_convert: %s\n", name));
+       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 (name != NULL &&
-           under_dfs(conn, name, local_path, sizeof(local_path)))
-       {
-               DEBUG(10,("%s is in dfs map.\n", name));
+       if(!conn->case_sensitive && stat_cache_lookup(conn, name, dirpath, &start, &st)) {
+               *pst = st;
+               return NT_STATUS_OK;
+       }
 
-               /* check for our own name */
-               if (StrCaseCmp(global_myname, name+1) > 0)
-               {
-                       return False;
+       /* 
+        * 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;
+       }
 
-               pstrcpy(name, local_path);
+       DEBUG(5,("unix_convert begin: name = %s, dirpath = %s, start = %s\n", name, dirpath, start));
 
-               DEBUG(10,("removed name: %s\n", name));
+       /* 
+        * 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;
        }
-       return unix_convert(name, conn, saved_last_component, bad_path, pst);
-}
 
-/****************************************************************************
-This routine is called to convert names from the dos namespace to unix
-namespace. It needs to handle any case conversions, mangling, format
-changes etc.
+       /* 
+        * is_mangled() was changed to look at an entire pathname, not 
+        * just a component. JRA.
+        */
 
-We assume that we have already done a chdir() to the right "root" directory
-for this service.
+       if (mangle_is_mangled(start, conn->params)) {
+               component_was_mangled = True;
+       }
 
-The function will return False if some part of the name except for the last
-part cannot be resolved
+       /* 
+        * 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 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.
+               if (saved_last_component != 0) {
+                       pstrcpy(saved_last_component, end ? end + 1 : start);
+               }
 
-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.
-****************************************************************************/
-BOOL unix_convert(char *name,connection_struct *conn,
-                               char *saved_last_component, 
-                               BOOL *bad_path, 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
+               /* The name cannot have a component of "." */
 
-  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(conn, name, dirpath, &start, &st)) {
-    if(pst)
-      *pst = st;
-    return True;
-  }
-
-  /* 
-   * stat the name - if it exists then we are all done!
-   */
-
-  if (conn->vfs_ops.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;
-#endif
+               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. */
 
-  /* 
-   * 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 (conn->vfs_ops.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);
-          }
+               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 calling check_reduced_name.
+ 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 ( (conn->vfs_ops.lstat(dos_to_unix(name,False),&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 = check_reduced_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);
 }