r23773: One pstring a day...
[tprouty/samba.git] / source / smbd / filename.c
index a71e8a806c17702c090832608bda0d9ff1dc0731..de15d923b8952db3ff6202f24ed8837164a10e25 100644 (file)
@@ -2,7 +2,7 @@
    Unix SMB/CIFS implementation.
    filename handling routines
    Copyright (C) Andrew Tridgell 1992-1998
-   Copyright (C) Jeremy Allison 1999-200
+   Copyright (C) Jeremy Allison 1999-2004
    Copyright (C) Ying Chen 2000
    
    This program is free software; you can redistribute it and/or modify
 
 #include "includes.h"
 
-extern BOOL case_sensitive;
-extern BOOL case_preserve;
-extern BOOL short_case_preserve;
-extern BOOL use_mangled_map;
-
-static BOOL scan_directory(const char *path, char *name,size_t maxlength,
-                          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(const char *name1, const char *name2)
+static BOOL fname_equal(const char *name1, const char *name2, BOOL case_sensitive)
 {
        /* Normal filename handling */
        if (case_sensitive)
@@ -52,15 +46,48 @@ static BOOL fname_equal(const char *name1, const char *name2)
  Mangle the 2nd name and check if it is then equal to the first name.
 ****************************************************************************/
 
-static BOOL mangled_equal(const char *name1, const char *name2, int snum)
+static BOOL mangled_equal(const char *name1, const char *name2,
+                         const struct share_params *p)
 {
        pstring tmpname;
        
        pstrcpy(tmpname, name2);
-       mangle_map(tmpname, True, False, snum);
+       mangle_map(tmpname, True, False, p);
        return strequal(name1, tmpname);
 }
 
+/****************************************************************************
+ Cope with the differing wildcard and non-wildcard error cases.
+****************************************************************************/
+
+static NTSTATUS determine_path_error(const char *name, BOOL allow_wcard_last_component)
+{
+       const char *p;
+
+       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;
+       }
+}
+       
 /****************************************************************************
 This routine is called to convert names from the dos namespace to unix
 namespace. It needs to handle any case conversions, mangling, format
@@ -69,18 +96,20 @@ changes etc.
 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
@@ -88,8 +117,11 @@ 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(pstring 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;
@@ -98,17 +130,18 @@ BOOL unix_convert(pstring name,connection_struct *conn,char *saved_last_componen
        BOOL component_was_mangled = False;
        BOOL name_has_wildcard = False;
 
-       ZERO_STRUCTP(pst);
+       SET_STAT_INVALID(*pst);
 
        *dirpath = 0;
-       *bad_path = False;
-       if(saved_last_component)
+
+       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 True;
+               return NT_STATUS_OK;
        }
 
        DEBUG(5, ("unix_convert called on file \"%s\"\n", name));
@@ -137,7 +170,20 @@ BOOL unix_convert(pstring name,connection_struct *conn,char *saved_last_componen
        if (!*name) {
                name[0] = '.';
                name[1] = '\0';
-               return(True);
+               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);
+               }
        }
 
        /*
@@ -146,21 +192,32 @@ BOOL unix_convert(pstring name,connection_struct *conn,char *saved_last_componen
 
        if(saved_last_component) {
                end = strrchr_m(name, '/');
-               if(end)
+               if (end) {
                        pstrcpy(saved_last_component, end + 1);
-               else
+               } else {
                        pstrcpy(saved_last_component, name);
+               }
        }
 
-       if (!case_sensitive && (!case_preserve || (mangle_is_8_3(name, False) && !short_case_preserve)))
-               strnorm(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(!case_sensitive && stat_cache_lookup(conn, name, dirpath, &start, &st)) {
+       if(!conn->case_sensitive && stat_cache_lookup(conn, name, dirpath, &start, &st)) {
                *pst = st;
-               return True;
+               return NT_STATUS_OK;
        }
 
        /* 
@@ -168,10 +225,22 @@ BOOL unix_convert(pstring name,connection_struct *conn,char *saved_last_componen
         */
 
        if (SMB_VFS_STAT(conn,name,&st) == 0) {
-               stat_cache_add(orig_path, name);
+               /* 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(True);
+               return NT_STATUS_OK;
        }
 
        DEBUG(5,("unix_convert begin: name = %s, dirpath = %s, start = %s\n", name, dirpath, start));
@@ -181,18 +250,20 @@ BOOL unix_convert(pstring name,connection_struct *conn,char *saved_last_componen
         * sensitive then searching won't help.
         */
 
-       if (case_sensitive && !mangle_is_mangled(name) && !use_mangled_map)
-               return(False);
-
-       name_has_wildcard = ms_has_wild(start);
+       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))
+       if (mangle_is_mangled(start, conn->params)) {
                component_was_mangled = True;
+       }
 
        /* 
         * Now we need to recursively match the name against the real 
@@ -208,16 +279,43 @@ BOOL unix_convert(pstring name,connection_struct *conn,char *saved_last_componen
                /* 
                 * Pinpoint the end of this section of the filename.
                 */
-               end = strchr_m(start, '/');
+               end = strchr(start, '/'); /* mb safe. '/' can't be in any encoded char. */
 
                /* 
                 * Chop the name at this point.
                 */
-               if (end) 
+               if (end) {
                        *end = 0;
+               }
 
-               if(saved_last_component != 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.
@@ -234,7 +332,14 @@ BOOL unix_convert(pstring name,connection_struct *conn,char *saved_last_componen
                                 */
                                DEBUG(5,("Not a dir %s\n",start));
                                *end = '/';
-                               return(False);
+                               /* 
+                                * 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) {
@@ -251,7 +356,7 @@ BOOL unix_convert(pstring name,connection_struct *conn,char *saved_last_componen
                        pstring rest;
 
                        /* Stat failed - ensure we don't use it. */
-                       ZERO_STRUCT(st);
+                       SET_STAT_INVALID(st);
                        *rest = 0;
 
                        /*
@@ -259,18 +364,19 @@ BOOL unix_convert(pstring name,connection_struct *conn,char *saved_last_componen
                         * later.
                         */
 
-                       if (end)
+                       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 (ms_has_wild(start) || 
-                           !scan_directory(dirpath, start, 
-                                           sizeof(pstring) - 1 - (start - name), 
-                                           conn, 
-                                           end?True:False)) {
+                       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.
@@ -285,32 +391,49 @@ BOOL unix_convert(pstring name,connection_struct *conn,char *saved_last_componen
                                         * Windows applications depend on the difference between
                                         * these two errors.
                                         */
-                                       *bad_path = True;
-                                       return(False);
+
+                                       /* 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);
                                }
              
-                               /* 
+                               /* 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 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.
+                                * 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 (!case_preserve && (!strhasupper(start) || !strhaslower(start)))             
-                                       strnorm(start);
+                               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)) {
-                                       mangle_check_cache( start );
+                               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(True); 
+                               return NT_STATUS_OK;
                        }
 
                        /* 
@@ -321,7 +444,7 @@ BOOL unix_convert(pstring name,connection_struct *conn,char *saved_last_componen
                                end = start + strlen(start);
                                if (!safe_strcat(start, "/", sizeof(pstring) - 1 - (start - name)) ||
                                    !safe_strcat(start, rest, sizeof(pstring) - 1 - (start - name))) {
-                                       return False;
+                                       return map_nt_error_from_unix(ENAMETOOLONG);
                                }
                                *end = '\0';
                        } else {
@@ -334,16 +457,23 @@ BOOL unix_convert(pstring name,connection_struct *conn,char *saved_last_componen
                                if (SMB_VFS_STAT(conn,name, &st) == 0) {
                                        *pst = st;
                                } else {
-                                       ZERO_STRUCT(st);
+                                       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)
+               if (*dirpath) {
                        pstrcat(dirpath,"/");
+               }
 
                pstrcat(dirpath,start);
 
@@ -352,14 +482,16 @@ BOOL unix_convert(pstring name,connection_struct *conn,char *saved_last_componen
                 * as this can change the size.
                 */
                
-               if(!component_was_mangled && !name_has_wildcard)
-                       stat_cache_add(orig_path, dirpath);
+               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)
+               if (end) {
                        *end = '/';
+               }
        }
   
        /*
@@ -367,58 +499,44 @@ BOOL unix_convert(pstring name,connection_struct *conn,char *saved_last_componen
         * 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.
         */
 
        DEBUG(5,("conversion finished %s -> %s\n",orig_path, name));
-       return(True);
+       return NT_STATUS_OK;
 }
 
 /****************************************************************************
- Check a filename - possibly caling reducename.
+ 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(pstring name,connection_struct *conn)
+NTSTATUS check_name(connection_struct *conn, const pstring name)
 {
-       BOOL ret;
-
-       errno = 0;
-
        if (IS_VETO_PATH(conn, name))  {
-               if(strcmp(name, ".") && strcmp(name, "..")) {
-                       DEBUG(5,("file path name %s vetoed\n",name));
-                       return(0);
+               /* 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);
                }
        }
 
-       ret = reduce_name(conn,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 ( (SMB_VFS_LSTAT(conn,name,&statbuf) != -1) &&
-                               (S_ISLNK(statbuf.st_mode)) ) {
-                       DEBUG(3,("check_name: denied: file path name %s is a symlink\n",name));
-                       ret=0; 
+       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;
                }
        }
-#endif
 
-       if (!ret)
-               DEBUG(5,("check_name on %s failed\n",name));
-
-       return(ret);
+       return NT_STATUS_OK;
 }
 
 /****************************************************************************
@@ -426,44 +544,52 @@ BOOL check_name(pstring name,connection_struct *conn)
  If the name looks like a mangled name then try via the mangling functions
 ****************************************************************************/
 
-static BOOL scan_directory(const char *path, char *name, size_t maxlength, 
-                          connection_struct *conn,BOOL docache)
+static BOOL scan_directory(connection_struct *conn, const char *path, char *name, size_t maxlength)
 {
-       void *cur_dir;
+       struct smb_Dir *cur_dir;
        const char *dname;
        BOOL mangled;
+       long curpos;
 
-       mangled = mangle_is_mangled(name);
+       mangled = mangle_is_mangled(name, conn->params);
 
        /* handle null paths */
        if (*path == 0)
                path = ".";
 
-       if (docache && (dname = DirCacheCheck(path,name,SNUM(conn)))) {
-               safe_strcpy(name, dname, maxlength);    
-               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 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)
-               mangled = !mangle_check_cache( name );
+
+       if (mangled && !conn->case_sensitive) {
+               mangled = !mangle_check_cache( name, maxlength, conn->params);
+       }
 
        /* open the directory */
-       if (!(cur_dir = OpenDir(conn, path, True))) {
+       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 */
-       while ((dname = ReadDirName(cur_dir))) {
-               if (*dname == '.' && (strequal(dname,".") || strequal(dname,"..")))
+       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.
@@ -476,10 +602,8 @@ static BOOL scan_directory(const char *path, char *name, size_t maxlength,
                 * against unmangled name.
                 */
 
-               if ((mangled && mangled_equal(name,dname,SNUM(conn))) || fname_equal(name, dname)) {
+               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 */
-                       if (docache)
-                               DirCacheAdd(path,name,dname,SNUM(conn));
                        safe_strcpy(name, dname, maxlength);
                        CloseDir(cur_dir);
                        return(True);
@@ -487,5 +611,6 @@ static BOOL scan_directory(const char *path, char *name, size_t maxlength,
        }
 
        CloseDir(cur_dir);
+       errno = ENOENT;
        return(False);
 }