r20844: Somewhat radical change - this may break the build (I will
[samba.git] / source3 / smbd / dosmode.c
index 7199b3ebbf3e6d69daa8d7fd81d461631502def8..ff4291c08c8183e860d73066f62a5b159cb2c769 100644 (file)
@@ -2,6 +2,7 @@
    Unix SMB/CIFS implementation.
    dos mode handling functions
    Copyright (C) Andrew Tridgell 1992-1998
+   Copyright (C) James Peach 2006
    
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 
 #include "includes.h"
 
+static int set_sparse_flag(const SMB_STRUCT_STAT * const sbuf)
+{
+#if defined (HAVE_STAT_ST_BLOCKS) && defined(STAT_ST_BLOCKSIZE)
+       if (sbuf->st_size > sbuf->st_blocks * (SMB_OFF_T)STAT_ST_BLOCKSIZE) {
+               return FILE_ATTRIBUTE_SPARSE;
+       }
+#endif
+       return 0;
+}
+
+/****************************************************************************
+ Work out whether this file is offline
+****************************************************************************/
+
+static uint32 set_offline_flag(connection_struct *conn, const char *const path)
+{
+       if (ISDOT(path) || ISDOTDOT(path)) {
+               return 0;
+       }
+
+       if (!lp_dmapi_support(SNUM(conn)) || !dmapi_have_session()) {
+               return 0;
+       }
+
+       return dmapi_file_flags(path);
+}
+
 /****************************************************************************
  Change a dos mode to a unix mode.
     Base permission for files:
-         if inheriting
+         if creating file and inheriting (i.e. parent_dir != NULL)
            apply read/write bits from parent directory.
          else   
            everybody gets read bit set
          }
 ****************************************************************************/
 
-mode_t unix_mode(connection_struct *conn, int dosmode, const char *fname)
+mode_t unix_mode(connection_struct *conn, int dosmode, const char *fname,
+                const char *inherit_from_dir)
 {
        mode_t result = (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH);
-       mode_t dir_mode = 0; /* Mode of the parent directory if inheriting. */
+       mode_t dir_mode = 0; /* Mode of the inherit_from directory if
+                             * inheriting. */
 
        if (!lp_store_dos_attributes(SNUM(conn)) && IS_DOS_READONLY(dosmode)) {
                result &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
        }
 
-       if (fname && lp_inherit_perms(SNUM(conn))) {
-               char *dname;
+       if (fname && (inherit_from_dir != NULL)
+           && lp_inherit_perms(SNUM(conn))) {
                SMB_STRUCT_STAT sbuf;
 
-               dname = parent_dirname(fname);
-               DEBUG(2,("unix_mode(%s) inheriting from %s\n",fname,dname));
-               if (SMB_VFS_STAT(conn,dname,&sbuf) != 0) {
-                       DEBUG(4,("unix_mode(%s) failed, [dir %s]: %s\n",fname,dname,strerror(errno)));
+               DEBUG(2, ("unix_mode(%s) inheriting from %s\n", fname,
+                         inherit_from_dir));
+               if (SMB_VFS_STAT(conn, inherit_from_dir, &sbuf) != 0) {
+                       DEBUG(4,("unix_mode(%s) failed, [dir %s]: %s\n", fname,
+                                inherit_from_dir, strerror(errno)));
                        return(0);      /* *** shouldn't happen! *** */
                }
 
@@ -116,13 +147,23 @@ mode_t unix_mode(connection_struct *conn, int dosmode, const char *fname)
  Change a unix mode to a dos mode.
 ****************************************************************************/
 
-uint32 dos_mode_from_sbuf(connection_struct *conn, SMB_STRUCT_STAT *sbuf)
+static uint32 dos_mode_from_sbuf(connection_struct *conn, const char *path, SMB_STRUCT_STAT *sbuf)
 {
        int result = 0;
+       enum mapreadonly_options ro_opts = (enum mapreadonly_options)lp_map_readonly(SNUM(conn));
+
+       if (ro_opts == MAP_READONLY_YES) {
+               /* Original Samba method - map inverse of user "w" bit. */
+               if ((sbuf->st_mode & S_IWUSR) == 0) {
+                       result |= aRONLY;
+               }
+       } else if (ro_opts == MAP_READONLY_PERMISSIONS) {
+               /* Check actual permissions for read-only. */
+               if (!can_write_to_file(conn, path, sbuf)) {
+                       result |= aRONLY;
+               }
+       } /* Else never set the readonly bit. */
 
-       if ((sbuf->st_mode & S_IWUSR) == 0)
-               result |= aRONLY;
-       
        if (MAP_ARCHIVE(conn) && ((sbuf->st_mode & S_IXUSR) != 0))
                result |= aARCH;
 
@@ -135,11 +176,7 @@ uint32 dos_mode_from_sbuf(connection_struct *conn, SMB_STRUCT_STAT *sbuf)
        if (S_ISDIR(sbuf->st_mode))
                result = aDIR | (result & aRONLY);
 
-#if defined (HAVE_STAT_ST_BLOCKS) && defined(STAT_ST_BLOCKSIZE)
-       if (sbuf->st_size > sbuf->st_blocks * (SMB_OFF_T)STAT_ST_BLOCKSIZE) {
-               result |= FILE_ATTRIBUTE_SPARSE;
-       }
-#endif
+       result |= set_sparse_flag(sbuf);
  
 #ifdef S_ISLNK
 #if LINKS_READ_ONLY
@@ -174,12 +211,13 @@ static BOOL get_ea_dos_attribute(connection_struct *conn, const char *path,SMB_S
                return False;
        }
 
-       *pattr = 0;
+       /* Don't reset pattr to zero as we may already have filename-based attributes we
+          need to preserve. */
 
        sizeret = SMB_VFS_GETXATTR(conn, path, SAMBA_XATTR_DOS_ATTRIB, attrstr, sizeof(attrstr));
        if (sizeret == -1) {
 #if defined(ENOTSUP) && defined(ENOATTR)
-               if ((errno != ENOTSUP) && (errno != ENOATTR) && (errno != EACCES)) {
+               if ((errno != ENOTSUP) && (errno != ENOATTR) && (errno != EACCES) && (errno != EPERM)) {
                        DEBUG(1,("get_ea_dos_attributes: Cannot get attribute from EA on file %s: Error = %s\n",
                                path, strerror(errno) ));
                        set_store_dos_attributes(SNUM(conn), False);
@@ -257,8 +295,7 @@ static BOOL set_ea_dos_attribute(connection_struct *conn, const char *path, SMB_
                 * are not violating security in doing the setxattr.
                 */
 
-               fsp = open_file_fchmod(conn,path,sbuf);
-               if (!fsp)
+               if (!NT_STATUS_IS_OK(open_file_fchmod(conn,path,sbuf,&fsp)))
                        return ret;
                become_root();
                if (SMB_VFS_SETXATTR(conn, path, SAMBA_XATTR_DOS_ATTRIB, attrstr, strlen(attrstr), 0) == 0) {
@@ -273,39 +310,96 @@ static BOOL set_ea_dos_attribute(connection_struct *conn, const char *path, SMB_
 }
 
 /****************************************************************************
- Change a unix mode to a dos mode.
+ Change a unix mode to a dos mode for an ms dfs link.
 ****************************************************************************/
 
-uint32 dos_mode(connection_struct *conn, const char *path,SMB_STRUCT_STAT *sbuf)
+uint32 dos_mode_msdfs(connection_struct *conn, const char *path,SMB_STRUCT_STAT *sbuf)
 {
        uint32 result = 0;
 
-       DEBUG(8,("dos_mode: %s\n", path));
+       DEBUG(8,("dos_mode_msdfs: %s\n", path));
 
        if (!VALID_STAT(*sbuf)) {
                return 0;
        }
 
-       /* Get the DOS attributes from an EA by preference. */
-       if (get_ea_dos_attribute(conn, path, sbuf, &result)) {
-               return result;
+       /* First do any modifications that depend on the path name. */
+       /* hide files with a name starting with a . */
+       if (lp_hide_dot_files(SNUM(conn))) {
+               const char *p = strrchr_m(path,'/');
+               if (p) {
+                       p++;
+               } else {
+                       p = path;
+               }
+               
+               if (p[0] == '.' && p[1] != '.' && p[1] != 0) {
+                       result |= aHIDDEN;
+               }
+       }
+       
+       result |= dos_mode_from_sbuf(conn, path, sbuf);
+
+       /* Optimization : Only call is_hidden_path if it's not already
+          hidden. */
+       if (!(result & aHIDDEN) && IS_HIDDEN_PATH(conn,path)) {
+               result |= aHIDDEN;
        }
 
-       result = dos_mode_from_sbuf(conn, sbuf);
+       DEBUG(8,("dos_mode_msdfs returning "));
+
+       if (result & aHIDDEN) DEBUG(8, ("h"));
+       if (result & aRONLY ) DEBUG(8, ("r"));
+       if (result & aSYSTEM) DEBUG(8, ("s"));
+       if (result & aDIR   ) DEBUG(8, ("d"));
+       if (result & aARCH  ) DEBUG(8, ("a"));
+       if (result & FILE_ATTRIBUTE_SPARSE ) DEBUG(8, ("[sparse]"));
+       
+       DEBUG(8,("\n"));
+
+       return(result);
+}
+
+/****************************************************************************
+ Change a unix mode to a dos mode.
+****************************************************************************/
+
+uint32 dos_mode(connection_struct *conn, const char *path,SMB_STRUCT_STAT *sbuf)
+{
+       uint32 result = 0;
+
+       DEBUG(8,("dos_mode: %s\n", path));
+
+       if (!VALID_STAT(*sbuf)) {
+               return 0;
+       }
 
-       /* Now do any modifications that depend on the path name. */
+       /* First do any modifications that depend on the path name. */
        /* hide files with a name starting with a . */
        if (lp_hide_dot_files(SNUM(conn))) {
                const char *p = strrchr_m(path,'/');
-               if (p)
+               if (p) {
                        p++;
-               else
+               } else {
                        p = path;
+               }
                
-               if (p[0] == '.' && p[1] != '.' && p[1] != 0)
+               if (p[0] == '.' && p[1] != '.' && p[1] != 0) {
                        result |= aHIDDEN;
+               }
        }
        
+       /* Get the DOS attributes from an EA by preference. */
+       if (get_ea_dos_attribute(conn, path, sbuf, &result)) {
+               result |= set_sparse_flag(sbuf);
+       } else {
+               result |= dos_mode_from_sbuf(conn, path, sbuf);
+       }
+
+       if (S_ISREG(sbuf->st_mode)) {
+               result |= set_offline_flag(conn, path);
+       }
+
        /* Optimization : Only call is_hidden_path if it's not already
           hidden. */
        if (!(result & aHIDDEN) && IS_HIDDEN_PATH(conn,path)) {
@@ -319,6 +413,7 @@ uint32 dos_mode(connection_struct *conn, const char *path,SMB_STRUCT_STAT *sbuf)
        if (result & aSYSTEM) DEBUG(8, ("s"));
        if (result & aDIR   ) DEBUG(8, ("d"));
        if (result & aARCH  ) DEBUG(8, ("a"));
+       if (result & FILE_ATTRIBUTE_SPARSE ) DEBUG(8, ("[sparse]"));
        
        DEBUG(8,("\n"));
 
@@ -329,7 +424,9 @@ uint32 dos_mode(connection_struct *conn, const char *path,SMB_STRUCT_STAT *sbuf)
  chmod a file - but preserve some bits.
 ********************************************************************/
 
-int file_set_dosmode(connection_struct *conn, const char *fname, uint32 dosmode, SMB_STRUCT_STAT *st)
+int file_set_dosmode(connection_struct *conn, const char *fname,
+                    uint32 dosmode, SMB_STRUCT_STAT *st,
+                    const char *parent_dir)
 {
        SMB_STRUCT_STAT st1;
        int mask=0;
@@ -337,8 +434,11 @@ int file_set_dosmode(connection_struct *conn, const char *fname, uint32 dosmode,
        mode_t unixmode;
        int ret = -1;
 
+       /* We only allow READONLY|HIDDEN|SYSTEM|DIRECTORY|ARCHIVE here. */
+       dosmode &= SAMBA_ATTRIBUTES_MASK;
+
        DEBUG(10,("file_set_dosmode: setting dos mode 0x%x on file %s\n", dosmode, fname));
-       if (!st) {
+       if (!st || (st && !VALID_STAT(*st))) {
                st = &st1;
                if (SMB_VFS_STAT(conn,fname,st))
                        return(-1);
@@ -359,7 +459,7 @@ int file_set_dosmode(connection_struct *conn, const char *fname, uint32 dosmode,
                return 0;
        }
 
-       unixmode = unix_mode(conn,dosmode,fname);
+       unixmode = unix_mode(conn,dosmode,fname, parent_dir);
 
        /* preserve the s bits */
        mask |= (S_ISUID | S_ISGID);
@@ -414,11 +514,11 @@ int file_set_dosmode(connection_struct *conn, const char *fname, uint32 dosmode,
                 * holding. We need to review this.... may need to
                 * break batch oplocks open by others. JRA.
                 */
-               files_struct *fsp = open_file_fchmod(conn,fname,st);
-               if (!fsp)
+               files_struct *fsp;
+               if (!NT_STATUS_IS_OK(open_file_fchmod(conn,fname,st,&fsp)))
                        return -1;
                become_root();
-               ret = SMB_VFS_FCHMOD(fsp, fsp->fd, unixmode);
+               ret = SMB_VFS_FCHMOD(fsp, fsp->fh->fd, unixmode);
                unbecome_root();
                close_file_fchmod(fsp);
        }
@@ -431,13 +531,24 @@ int file_set_dosmode(connection_struct *conn, const char *fname, uint32 dosmode,
  than POSIX.
 *******************************************************************/
 
-int file_utime(connection_struct *conn, char *fname, struct utimbuf *times)
+int file_utime(connection_struct *conn, const char *fname, struct utimbuf *times)
 {
-       extern struct current_user current_user;
-       SMB_STRUCT_STAT sb;
+       SMB_STRUCT_STAT sbuf;
        int ret = -1;
 
        errno = 0;
+       ZERO_STRUCT(sbuf);
+
+       /* Don't update the time on read-only shares */
+       /* We need this as set_filetime (which can be called on
+          close and other paths) can end up calling this function
+          without the NEED_WRITE protection. Found by : 
+          Leo Weppelman <leo@wau.mis.ah.nl>
+       */
+
+       if (!CAN_WRITE(conn)) {
+               return 0;
+       }
 
        if(SMB_VFS_UTIME(conn,fname, times) == 0)
                return 0;
@@ -454,21 +565,12 @@ int file_utime(connection_struct *conn, char *fname, struct utimbuf *times)
           (as DOS does).
         */
 
-       if(SMB_VFS_STAT(conn,fname,&sb) != 0)
-               return -1;
-
        /* Check if we have write access. */
-       if (CAN_WRITE(conn)) {
-               if (((sb.st_mode & S_IWOTH) || conn->admin_user ||
-                       ((sb.st_mode & S_IWUSR) && current_user.uid==sb.st_uid) ||
-                       ((sb.st_mode & S_IWGRP) &&
-                               in_group(sb.st_gid,current_user.gid,
-                                       current_user.ngroups,current_user.groups)))) {
-                       /* We are allowed to become root and change the filetime. */
-                       become_root();
-                       ret = SMB_VFS_UTIME(conn,fname, times);
-                       unbecome_root();
-               }
+       if (can_write_to_file(conn, fname, &sbuf)) {
+               /* We are allowed to become root and change the filetime. */
+               become_root();
+               ret = SMB_VFS_UTIME(conn,fname, times);
+               unbecome_root();
        }
 
        return ret;
@@ -478,7 +580,7 @@ int file_utime(connection_struct *conn, char *fname, struct utimbuf *times)
  Change a filetime - possibly allowing DOS semantics.
 *******************************************************************/
 
-BOOL set_filetime(connection_struct *conn, char *fname, time_t mtime)
+BOOL set_filetime(connection_struct *conn, const char *fname, time_t mtime)
 {
        struct utimbuf times;