r10276: Fix for bug #3104 from Leo Weppelman <leo@wau.mis.ah.nl>.
[tprouty/samba.git] / source / 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         /* Get the DOS attributes from an EA by preference. */
301         if (get_ea_dos_attribute(conn, path, sbuf, &result)) {
302                 result |= set_sparse_flag(sbuf);
303                 return result;
304         }
305
306         result = dos_mode_from_sbuf(conn, path, sbuf);
307
308         /* Now do any modifications that depend on the path name. */
309         /* hide files with a name starting with a . */
310         if (lp_hide_dot_files(SNUM(conn))) {
311                 const char *p = strrchr_m(path,'/');
312                 if (p)
313                         p++;
314                 else
315                         p = path;
316                 
317                 if (p[0] == '.' && p[1] != '.' && p[1] != 0)
318                         result |= aHIDDEN;
319         }
320         
321         /* Optimization : Only call is_hidden_path if it's not already
322            hidden. */
323         if (!(result & aHIDDEN) && IS_HIDDEN_PATH(conn,path)) {
324                 result |= aHIDDEN;
325         }
326
327         DEBUG(8,("dos_mode returning "));
328
329         if (result & aHIDDEN) DEBUG(8, ("h"));
330         if (result & aRONLY ) DEBUG(8, ("r"));
331         if (result & aSYSTEM) DEBUG(8, ("s"));
332         if (result & aDIR   ) DEBUG(8, ("d"));
333         if (result & aARCH  ) DEBUG(8, ("a"));
334         
335         DEBUG(8,("\n"));
336
337         return(result);
338 }
339
340 /*******************************************************************
341  chmod a file - but preserve some bits.
342 ********************************************************************/
343
344 int file_set_dosmode(connection_struct *conn, const char *fname, uint32 dosmode, SMB_STRUCT_STAT *st, BOOL creating_file)
345 {
346         SMB_STRUCT_STAT st1;
347         int mask=0;
348         mode_t tmp;
349         mode_t unixmode;
350         int ret = -1;
351
352         DEBUG(10,("file_set_dosmode: setting dos mode 0x%x on file %s\n", dosmode, fname));
353         if (!st || (st && !VALID_STAT(*st))) {
354                 st = &st1;
355                 if (SMB_VFS_STAT(conn,fname,st))
356                         return(-1);
357         }
358
359         get_acl_group_bits(conn, fname, &st->st_mode);
360
361         if (S_ISDIR(st->st_mode))
362                 dosmode |= aDIR;
363         else
364                 dosmode &= ~aDIR;
365
366         if (dos_mode(conn,fname,st) == dosmode)
367                 return(0);
368
369         /* Store the DOS attributes in an EA by preference. */
370         if (set_ea_dos_attribute(conn, fname, st, dosmode)) {
371                 return 0;
372         }
373
374         unixmode = unix_mode(conn,dosmode,fname, creating_file);
375
376         /* preserve the s bits */
377         mask |= (S_ISUID | S_ISGID);
378
379         /* preserve the t bit */
380 #ifdef S_ISVTX
381         mask |= S_ISVTX;
382 #endif
383
384         /* possibly preserve the x bits */
385         if (!MAP_ARCHIVE(conn))
386                 mask |= S_IXUSR;
387         if (!MAP_SYSTEM(conn))
388                 mask |= S_IXGRP;
389         if (!MAP_HIDDEN(conn))
390                 mask |= S_IXOTH;
391
392         unixmode |= (st->st_mode & mask);
393
394         /* if we previously had any r bits set then leave them alone */
395         if ((tmp = st->st_mode & (S_IRUSR|S_IRGRP|S_IROTH))) {
396                 unixmode &= ~(S_IRUSR|S_IRGRP|S_IROTH);
397                 unixmode |= tmp;
398         }
399
400         /* if we previously had any w bits set then leave them alone 
401                 whilst adding in the new w bits, if the new mode is not rdonly */
402         if (!IS_DOS_READONLY(dosmode)) {
403                 unixmode |= (st->st_mode & (S_IWUSR|S_IWGRP|S_IWOTH));
404         }
405
406         if ((ret = SMB_VFS_CHMOD(conn,fname,unixmode)) == 0)
407                 return 0;
408
409         if((errno != EPERM) && (errno != EACCES))
410                 return -1;
411
412         if(!lp_dos_filemode(SNUM(conn)))
413                 return -1;
414
415         /* We want DOS semantics, ie allow non owner with write permission to change the
416                 bits on a file. Just like file_utime below.
417         */
418
419         /* Check if we have write access. */
420         if (CAN_WRITE(conn)) {
421                 /*
422                  * We need to open the file with write access whilst
423                  * still in our current user context. This ensures we
424                  * are not violating security in doing the fchmod.
425                  * This file open does *not* break any oplocks we are
426                  * holding. We need to review this.... may need to
427                  * break batch oplocks open by others. JRA.
428                  */
429                 files_struct *fsp = open_file_fchmod(conn,fname,st);
430                 if (!fsp)
431                         return -1;
432                 become_root();
433                 ret = SMB_VFS_FCHMOD(fsp, fsp->fh->fd, unixmode);
434                 unbecome_root();
435                 close_file_fchmod(fsp);
436         }
437
438         return( ret );
439 }
440
441 /*******************************************************************
442  Wrapper around dos_utime that possibly allows DOS semantics rather
443  than POSIX.
444 *******************************************************************/
445
446 int file_utime(connection_struct *conn, const char *fname, struct utimbuf *times)
447 {
448         SMB_STRUCT_STAT sbuf;
449         int ret = -1;
450
451         errno = 0;
452         ZERO_STRUCT(sbuf);
453
454         /* Don't update the time on read-only shares */
455         /* We need this as set_filetime (which can be called on
456            close and other paths) can end up calling this function
457            without the NEED_WRITE protection. Found by : 
458            Leo Weppelman <leo@wau.mis.ah.nl>
459         */
460
461         if (!CAN_WRITE(conn)) {
462                 return 0;
463         }
464
465         if(SMB_VFS_UTIME(conn,fname, times) == 0)
466                 return 0;
467
468         if((errno != EPERM) && (errno != EACCES))
469                 return -1;
470
471         if(!lp_dos_filetimes(SNUM(conn)))
472                 return -1;
473
474         /* We have permission (given by the Samba admin) to
475            break POSIX semantics and allow a user to change
476            the time on a file they don't own but can write to
477            (as DOS does).
478          */
479
480         /* Check if we have write access. */
481         if (can_write_to_file(conn, fname, &sbuf)) {
482                 /* We are allowed to become root and change the filetime. */
483                 become_root();
484                 ret = SMB_VFS_UTIME(conn,fname, times);
485                 unbecome_root();
486         }
487
488         return ret;
489 }
490   
491 /*******************************************************************
492  Change a filetime - possibly allowing DOS semantics.
493 *******************************************************************/
494
495 BOOL set_filetime(connection_struct *conn, const char *fname, time_t mtime)
496 {
497         struct utimbuf times;
498
499         if (null_mtime(mtime))
500                 return(True);
501
502         times.modtime = times.actime = mtime;
503
504         if (file_utime(conn, fname, &times)) {
505                 DEBUG(4,("set_filetime(%s) failed: %s\n",fname,strerror(errno)));
506                 return False;
507         }
508   
509         return(True);
510