r4007: Fix bug #2088 - ensure inherit permissions is only applied on a new file,
[samba.git] / source3 / smbd / dosmode.c
1 /* 
2    Unix SMB/CIFS implementation.
3    dos mode handling functions
4    Copyright (C) Andrew Tridgell 1992-1998
5    
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10    
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15    
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21 #include "includes.h"
22
23 /****************************************************************************
24  Change a dos mode to a unix mode.
25     Base permission for files:
26          if creating file and inheriting
27            apply read/write bits from parent directory.
28          else   
29            everybody gets read bit set
30          dos readonly is represented in unix by removing everyone's write bit
31          dos archive is represented in unix by the user's execute bit
32          dos system is represented in unix by the group's execute bit
33          dos hidden is represented in unix by the other's execute bit
34          if !inheriting {
35            Then apply create mask,
36            then add force bits.
37          }
38     Base permission for directories:
39          dos directory is represented in unix by unix's dir bit and the exec bit
40          if !inheriting {
41            Then apply create mask,
42            then add force bits.
43          }
44 ****************************************************************************/
45
46 mode_t unix_mode(connection_struct *conn, int dosmode, const char *fname, BOOL creating_file)
47 {
48         mode_t result = (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH);
49         mode_t dir_mode = 0; /* Mode of the parent directory if inheriting. */
50
51         if (!lp_store_dos_attributes(SNUM(conn)) && IS_DOS_READONLY(dosmode)) {
52                 result &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
53         }
54
55         if (fname && creating_file && lp_inherit_perms(SNUM(conn))) {
56                 char *dname;
57                 SMB_STRUCT_STAT sbuf;
58
59                 dname = parent_dirname(fname);
60                 DEBUG(2,("unix_mode(%s) inheriting from %s\n",fname,dname));
61                 if (SMB_VFS_STAT(conn,dname,&sbuf) != 0) {
62                         DEBUG(4,("unix_mode(%s) failed, [dir %s]: %s\n",fname,dname,strerror(errno)));
63                         return(0);      /* *** shouldn't happen! *** */
64                 }
65
66                 /* Save for later - but explicitly remove setuid bit for safety. */
67                 dir_mode = sbuf.st_mode & ~S_ISUID;
68                 DEBUG(2,("unix_mode(%s) inherit mode %o\n",fname,(int)dir_mode));
69                 /* Clear "result" */
70                 result = 0;
71         } 
72
73         if (IS_DOS_DIR(dosmode)) {
74                 /* We never make directories read only for the owner as under DOS a user
75                 can always create a file in a read-only directory. */
76                 result |= (S_IFDIR | S_IWUSR);
77
78                 if (dir_mode) {
79                         /* Inherit mode of parent directory. */
80                         result |= dir_mode;
81                 } else {
82                         /* Provisionally add all 'x' bits */
83                         result |= (S_IXUSR | S_IXGRP | S_IXOTH);                 
84
85                         /* Apply directory mask */
86                         result &= lp_dir_mask(SNUM(conn));
87                         /* Add in force bits */
88                         result |= lp_force_dir_mode(SNUM(conn));
89                 }
90         } else { 
91                 if (lp_map_archive(SNUM(conn)) && IS_DOS_ARCHIVE(dosmode))
92                         result |= S_IXUSR;
93
94                 if (lp_map_system(SNUM(conn)) && IS_DOS_SYSTEM(dosmode))
95                         result |= S_IXGRP;
96  
97                 if (lp_map_hidden(SNUM(conn)) && IS_DOS_HIDDEN(dosmode))
98                         result |= S_IXOTH;  
99
100                 if (dir_mode) {
101                         /* Inherit 666 component of parent directory mode */
102                         result |= dir_mode & (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH);
103                 } else {
104                         /* Apply mode mask */
105                         result &= lp_create_mask(SNUM(conn));
106                         /* Add in force bits */
107                         result |= lp_force_create_mode(SNUM(conn));
108                 }
109         }
110
111         DEBUG(3,("unix_mode(%s) returning 0%o\n",fname,(int)result ));
112         return(result);
113 }
114
115 /****************************************************************************
116  Change a unix mode to a dos mode.
117 ****************************************************************************/
118
119 uint32 dos_mode_from_sbuf(connection_struct *conn, SMB_STRUCT_STAT *sbuf)
120 {
121         int result = 0;
122
123         if ((sbuf->st_mode & S_IWUSR) == 0)
124                 result |= aRONLY;
125         
126         if (MAP_ARCHIVE(conn) && ((sbuf->st_mode & S_IXUSR) != 0))
127                 result |= aARCH;
128
129         if (MAP_SYSTEM(conn) && ((sbuf->st_mode & S_IXGRP) != 0))
130                 result |= aSYSTEM;
131         
132         if (MAP_HIDDEN(conn) && ((sbuf->st_mode & S_IXOTH) != 0))
133                 result |= aHIDDEN;   
134   
135         if (S_ISDIR(sbuf->st_mode))
136                 result = aDIR | (result & aRONLY);
137
138 #if defined (HAVE_STAT_ST_BLOCKS) && defined(STAT_ST_BLOCKSIZE)
139         if (sbuf->st_size > sbuf->st_blocks * (SMB_OFF_T)STAT_ST_BLOCKSIZE) {
140                 result |= FILE_ATTRIBUTE_SPARSE;
141         }
142 #endif
143  
144 #ifdef S_ISLNK
145 #if LINKS_READ_ONLY
146         if (S_ISLNK(sbuf->st_mode) && S_ISDIR(sbuf->st_mode))
147                 result |= aRONLY;
148 #endif
149 #endif
150
151         DEBUG(8,("dos_mode_from_sbuf returning "));
152
153         if (result & aHIDDEN) DEBUG(8, ("h"));
154         if (result & aRONLY ) DEBUG(8, ("r"));
155         if (result & aSYSTEM) DEBUG(8, ("s"));
156         if (result & aDIR   ) DEBUG(8, ("d"));
157         if (result & aARCH  ) DEBUG(8, ("a"));
158         
159         DEBUG(8,("\n"));
160         return result;
161 }
162
163 /****************************************************************************
164  Get DOS attributes from an EA.
165 ****************************************************************************/
166
167 static BOOL get_ea_dos_attribute(connection_struct *conn, const char *path,SMB_STRUCT_STAT *sbuf, uint32 *pattr)
168 {
169         ssize_t sizeret;
170         fstring attrstr;
171         unsigned int dosattr;
172
173         if (!lp_store_dos_attributes(SNUM(conn))) {
174                 return False;
175         }
176
177         *pattr = 0;
178
179         sizeret = SMB_VFS_GETXATTR(conn, path, SAMBA_XATTR_DOS_ATTRIB, attrstr, sizeof(attrstr));
180         if (sizeret == -1) {
181 #if defined(ENOTSUP) && defined(ENOATTR)
182                 if ((errno != ENOTSUP) && (errno != ENOATTR) && (errno != EACCES)) {
183                         DEBUG(1,("get_ea_dos_attributes: Cannot get attribute from EA on file %s: Error = %s\n",
184                                 path, strerror(errno) ));
185                         set_store_dos_attributes(SNUM(conn), False);
186                 }
187 #endif
188                 return False;
189         }
190         /* Null terminate string. */
191         attrstr[sizeret] = 0;
192         DEBUG(10,("get_ea_dos_attribute: %s attrstr = %s\n", path, attrstr));
193
194         if (sizeret < 2 || attrstr[0] != '0' || attrstr[1] != 'x' ||
195                         sscanf(attrstr, "%x", &dosattr) != 1) {
196                 DEBUG(1,("get_ea_dos_attributes: Badly formed DOSATTRIB on file %s - %s\n", path, attrstr));
197                 return False;
198         }
199
200         if (S_ISDIR(sbuf->st_mode)) {
201                 dosattr |= aDIR;
202         }
203         *pattr = (uint32)(dosattr & SAMBA_ATTRIBUTES_MASK);
204
205         DEBUG(8,("get_ea_dos_attribute returning (0x%x)", dosattr));
206
207         if (dosattr & aHIDDEN) DEBUG(8, ("h"));
208         if (dosattr & aRONLY ) DEBUG(8, ("r"));
209         if (dosattr & aSYSTEM) DEBUG(8, ("s"));
210         if (dosattr & aDIR   ) DEBUG(8, ("d"));
211         if (dosattr & aARCH  ) DEBUG(8, ("a"));
212         
213         DEBUG(8,("\n"));
214
215         return True;
216 }
217
218 /****************************************************************************
219  Set DOS attributes in an EA.
220 ****************************************************************************/
221
222 static BOOL set_ea_dos_attribute(connection_struct *conn, const char *path, SMB_STRUCT_STAT *sbuf, uint32 dosmode)
223 {
224         fstring attrstr;
225         files_struct *fsp = NULL;
226         BOOL ret = False;
227
228         if (!lp_store_dos_attributes(SNUM(conn))) {
229                 return False;
230         }
231
232         snprintf(attrstr, sizeof(attrstr)-1, "0x%x", dosmode & SAMBA_ATTRIBUTES_MASK);
233         if (SMB_VFS_SETXATTR(conn, path, SAMBA_XATTR_DOS_ATTRIB, attrstr, strlen(attrstr), 0) == -1) {
234                 if((errno != EPERM) && (errno != EACCES)) {
235                         if (errno == ENOSYS
236 #if defined(ENOTSUP)
237                                 || errno == ENOTSUP) {
238 #else
239                                 ) {
240 #endif
241                                 set_store_dos_attributes(SNUM(conn), False);
242                         }
243                         return False;
244                 }
245
246                 /* We want DOS semantics, ie allow non owner with write permission to change the
247                         bits on a file. Just like file_utime below.
248                 */
249
250                 /* Check if we have write access. */
251                 if(!CAN_WRITE(conn) || !lp_dos_filemode(SNUM(conn)))
252                         return False;
253
254                 /*
255                  * We need to open the file with write access whilst
256                  * still in our current user context. This ensures we
257                  * are not violating security in doing the setxattr.
258                  */
259
260                 fsp = open_file_fchmod(conn,path,sbuf);
261                 if (!fsp)
262                         return ret;
263                 become_root();
264                 if (SMB_VFS_SETXATTR(conn, path, SAMBA_XATTR_DOS_ATTRIB, attrstr, strlen(attrstr), 0) == 0) {
265                         ret = True;
266                 }
267                 unbecome_root();
268                 close_file_fchmod(fsp);
269                 return ret;
270         }
271         DEBUG(10,("set_ea_dos_attribute: set EA %s on file %s\n", attrstr, path));
272         return True;
273 }
274
275 /****************************************************************************
276  Change a unix mode to a dos mode.
277 ****************************************************************************/
278
279 uint32 dos_mode(connection_struct *conn, const char *path,SMB_STRUCT_STAT *sbuf)
280 {
281         uint32 result = 0;
282
283         DEBUG(8,("dos_mode: %s\n", path));
284
285         if (!VALID_STAT(*sbuf)) {
286                 return 0;
287         }
288
289         /* Get the DOS attributes from an EA by preference. */
290         if (get_ea_dos_attribute(conn, path, sbuf, &result)) {
291                 return result;
292         }
293
294         result = dos_mode_from_sbuf(conn, sbuf);
295
296         /* Now do any modifications that depend on the path name. */
297         /* hide files with a name starting with a . */
298         if (lp_hide_dot_files(SNUM(conn))) {
299                 const char *p = strrchr_m(path,'/');
300                 if (p)
301                         p++;
302                 else
303                         p = path;
304                 
305                 if (p[0] == '.' && p[1] != '.' && p[1] != 0)
306                         result |= aHIDDEN;
307         }
308         
309         /* Optimization : Only call is_hidden_path if it's not already
310            hidden. */
311         if (!(result & aHIDDEN) && IS_HIDDEN_PATH(conn,path)) {
312                 result |= aHIDDEN;
313         }
314
315         DEBUG(8,("dos_mode returning "));
316
317         if (result & aHIDDEN) DEBUG(8, ("h"));
318         if (result & aRONLY ) DEBUG(8, ("r"));
319         if (result & aSYSTEM) DEBUG(8, ("s"));
320         if (result & aDIR   ) DEBUG(8, ("d"));
321         if (result & aARCH  ) DEBUG(8, ("a"));
322         
323         DEBUG(8,("\n"));
324
325         return(result);
326 }
327
328 /*******************************************************************
329  chmod a file - but preserve some bits.
330 ********************************************************************/
331
332 int file_set_dosmode(connection_struct *conn, const char *fname, uint32 dosmode, SMB_STRUCT_STAT *st, BOOL creating_file)
333 {
334         SMB_STRUCT_STAT st1;
335         int mask=0;
336         mode_t tmp;
337         mode_t unixmode;
338         int ret = -1;
339
340         DEBUG(10,("file_set_dosmode: setting dos mode 0x%x on file %s\n", dosmode, fname));
341         if (!st || (st && !VALID_STAT(*st))) {
342                 st = &st1;
343                 if (SMB_VFS_STAT(conn,fname,st))
344                         return(-1);
345         }
346
347         get_acl_group_bits(conn, fname, &st->st_mode);
348
349         if (S_ISDIR(st->st_mode))
350                 dosmode |= aDIR;
351         else
352                 dosmode &= ~aDIR;
353
354         if (dos_mode(conn,fname,st) == dosmode)
355                 return(0);
356
357         /* Store the DOS attributes in an EA by preference. */
358         if (set_ea_dos_attribute(conn, fname, st, dosmode)) {
359                 return 0;
360         }
361
362         unixmode = unix_mode(conn,dosmode,fname, creating_file);
363
364         /* preserve the s bits */
365         mask |= (S_ISUID | S_ISGID);
366
367         /* preserve the t bit */
368 #ifdef S_ISVTX
369         mask |= S_ISVTX;
370 #endif
371
372         /* possibly preserve the x bits */
373         if (!MAP_ARCHIVE(conn))
374                 mask |= S_IXUSR;
375         if (!MAP_SYSTEM(conn))
376                 mask |= S_IXGRP;
377         if (!MAP_HIDDEN(conn))
378                 mask |= S_IXOTH;
379
380         unixmode |= (st->st_mode & mask);
381
382         /* if we previously had any r bits set then leave them alone */
383         if ((tmp = st->st_mode & (S_IRUSR|S_IRGRP|S_IROTH))) {
384                 unixmode &= ~(S_IRUSR|S_IRGRP|S_IROTH);
385                 unixmode |= tmp;
386         }
387
388         /* if we previously had any w bits set then leave them alone 
389                 whilst adding in the new w bits, if the new mode is not rdonly */
390         if (!IS_DOS_READONLY(dosmode)) {
391                 unixmode |= (st->st_mode & (S_IWUSR|S_IWGRP|S_IWOTH));
392         }
393
394         if ((ret = SMB_VFS_CHMOD(conn,fname,unixmode)) == 0)
395                 return 0;
396
397         if((errno != EPERM) && (errno != EACCES))
398                 return -1;
399
400         if(!lp_dos_filemode(SNUM(conn)))
401                 return -1;
402
403         /* We want DOS semantics, ie allow non owner with write permission to change the
404                 bits on a file. Just like file_utime below.
405         */
406
407         /* Check if we have write access. */
408         if (CAN_WRITE(conn)) {
409                 /*
410                  * We need to open the file with write access whilst
411                  * still in our current user context. This ensures we
412                  * are not violating security in doing the fchmod.
413                  * This file open does *not* break any oplocks we are
414                  * holding. We need to review this.... may need to
415                  * break batch oplocks open by others. JRA.
416                  */
417                 files_struct *fsp = open_file_fchmod(conn,fname,st);
418                 if (!fsp)
419                         return -1;
420                 become_root();
421                 ret = SMB_VFS_FCHMOD(fsp, fsp->fd, unixmode);
422                 unbecome_root();
423                 close_file_fchmod(fsp);
424         }
425
426         return( ret );
427 }
428
429 /*******************************************************************
430  Wrapper around dos_utime that possibly allows DOS semantics rather
431  than POSIX.
432 *******************************************************************/
433
434 int file_utime(connection_struct *conn, char *fname, struct utimbuf *times)
435 {
436         extern struct current_user current_user;
437         SMB_STRUCT_STAT sb;
438         int ret = -1;
439
440         errno = 0;
441
442         if(SMB_VFS_UTIME(conn,fname, times) == 0)
443                 return 0;
444
445         if((errno != EPERM) && (errno != EACCES))
446                 return -1;
447
448         if(!lp_dos_filetimes(SNUM(conn)))
449                 return -1;
450
451         /* We have permission (given by the Samba admin) to
452            break POSIX semantics and allow a user to change
453            the time on a file they don't own but can write to
454            (as DOS does).
455          */
456
457         if(SMB_VFS_STAT(conn,fname,&sb) != 0)
458                 return -1;
459
460         /* Check if we have write access. */
461         if (CAN_WRITE(conn)) {
462                 if (((sb.st_mode & S_IWOTH) || conn->admin_user ||
463                         ((sb.st_mode & S_IWUSR) && current_user.uid==sb.st_uid) ||
464                         ((sb.st_mode & S_IWGRP) &&
465                                 in_group(sb.st_gid,current_user.gid,
466                                         current_user.ngroups,current_user.groups)))) {
467                         /* We are allowed to become root and change the filetime. */
468                         become_root();
469                         ret = SMB_VFS_UTIME(conn,fname, times);
470                         unbecome_root();
471                 }
472         }
473
474         return ret;
475 }
476   
477 /*******************************************************************
478  Change a filetime - possibly allowing DOS semantics.
479 *******************************************************************/
480
481 BOOL set_filetime(connection_struct *conn, char *fname, time_t mtime)
482 {
483         struct utimbuf times;
484
485         if (null_mtime(mtime))
486                 return(True);
487
488         times.modtime = times.actime = mtime;
489
490         if (file_utime(conn, fname, &times)) {
491                 DEBUG(4,("set_filetime(%s) failed: %s\n",fname,strerror(errno)));
492                 return False;
493         }
494   
495         return(True);
496