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