8e3c9b4c91b20aed18ab375c643bf40471e23b37
[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    Copyright (C) James Peach 2006
6    
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3 of the License, or
10    (at your option) any later version.
11    
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16    
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <http://www.gnu.org/licenses/>.
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  Work out whether this file is offline
35 ****************************************************************************/
36
37 static uint32 set_offline_flag(connection_struct *conn, const char *const path)
38 {
39         if (ISDOT(path) || ISDOTDOT(path)) {
40                 return 0;
41         }
42
43         if (!lp_dmapi_support(SNUM(conn)) || !dmapi_have_session()) {
44                 return 0;
45         }
46
47         return dmapi_file_flags(path);
48 }
49
50 /****************************************************************************
51  Change a dos mode to a unix mode.
52     Base permission for files:
53          if creating file and inheriting (i.e. parent_dir != NULL)
54            apply read/write bits from parent directory.
55          else   
56            everybody gets read bit set
57          dos readonly is represented in unix by removing everyone's write bit
58          dos archive is represented in unix by the user's execute bit
59          dos system is represented in unix by the group's execute bit
60          dos hidden is represented in unix by the other's execute bit
61          if !inheriting {
62            Then apply create mask,
63            then add force bits.
64          }
65     Base permission for directories:
66          dos directory is represented in unix by unix's dir bit and the exec bit
67          if !inheriting {
68            Then apply create mask,
69            then add force bits.
70          }
71 ****************************************************************************/
72
73 mode_t unix_mode(connection_struct *conn, int dosmode, const char *fname,
74                  const char *inherit_from_dir)
75 {
76         mode_t result = (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH);
77         mode_t dir_mode = 0; /* Mode of the inherit_from directory if
78                               * inheriting. */
79
80         if (!lp_store_dos_attributes(SNUM(conn)) && IS_DOS_READONLY(dosmode)) {
81                 result &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
82         }
83
84         if (fname && (inherit_from_dir != NULL)
85             && lp_inherit_perms(SNUM(conn))) {
86                 SMB_STRUCT_STAT sbuf;
87
88                 DEBUG(2, ("unix_mode(%s) inheriting from %s\n", fname,
89                           inherit_from_dir));
90                 if (SMB_VFS_STAT(conn, inherit_from_dir, &sbuf) != 0) {
91                         DEBUG(4,("unix_mode(%s) failed, [dir %s]: %s\n", fname,
92                                  inherit_from_dir, strerror(errno)));
93                         return(0);      /* *** shouldn't happen! *** */
94                 }
95
96                 /* Save for later - but explicitly remove setuid bit for safety. */
97                 dir_mode = sbuf.st_mode & ~S_ISUID;
98                 DEBUG(2,("unix_mode(%s) inherit mode %o\n",fname,(int)dir_mode));
99                 /* Clear "result" */
100                 result = 0;
101         } 
102
103         if (IS_DOS_DIR(dosmode)) {
104                 /* We never make directories read only for the owner as under DOS a user
105                 can always create a file in a read-only directory. */
106                 result |= (S_IFDIR | S_IWUSR);
107
108                 if (dir_mode) {
109                         /* Inherit mode of parent directory. */
110                         result |= dir_mode;
111                 } else {
112                         /* Provisionally add all 'x' bits */
113                         result |= (S_IXUSR | S_IXGRP | S_IXOTH);                 
114
115                         /* Apply directory mask */
116                         result &= lp_dir_mask(SNUM(conn));
117                         /* Add in force bits */
118                         result |= lp_force_dir_mode(SNUM(conn));
119                 }
120         } else { 
121                 if (lp_map_archive(SNUM(conn)) && IS_DOS_ARCHIVE(dosmode))
122                         result |= S_IXUSR;
123
124                 if (lp_map_system(SNUM(conn)) && IS_DOS_SYSTEM(dosmode))
125                         result |= S_IXGRP;
126  
127                 if (lp_map_hidden(SNUM(conn)) && IS_DOS_HIDDEN(dosmode))
128                         result |= S_IXOTH;  
129
130                 if (dir_mode) {
131                         /* Inherit 666 component of parent directory mode */
132                         result |= dir_mode & (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH);
133                 } else {
134                         /* Apply mode mask */
135                         result &= lp_create_mask(SNUM(conn));
136                         /* Add in force bits */
137                         result |= lp_force_create_mode(SNUM(conn));
138                 }
139         }
140
141         DEBUG(3,("unix_mode(%s) returning 0%o\n",fname,(int)result ));
142         return(result);
143 }
144
145 /****************************************************************************
146  Change a unix mode to a dos mode.
147 ****************************************************************************/
148
149 static uint32 dos_mode_from_sbuf(connection_struct *conn, const char *path, SMB_STRUCT_STAT *sbuf)
150 {
151         int result = 0;
152         enum mapreadonly_options ro_opts = (enum mapreadonly_options)lp_map_readonly(SNUM(conn));
153
154         if (ro_opts == MAP_READONLY_YES) {
155                 /* Original Samba method - map inverse of user "w" bit. */
156                 if ((sbuf->st_mode & S_IWUSR) == 0) {
157                         result |= aRONLY;
158                 }
159         } else if (ro_opts == MAP_READONLY_PERMISSIONS) {
160                 /* Check actual permissions for read-only. */
161                 if (!can_write_to_file(conn, path, sbuf)) {
162                         result |= aRONLY;
163                 }
164         } /* Else never set the readonly bit. */
165
166         if (MAP_ARCHIVE(conn) && ((sbuf->st_mode & S_IXUSR) != 0))
167                 result |= aARCH;
168
169         if (MAP_SYSTEM(conn) && ((sbuf->st_mode & S_IXGRP) != 0))
170                 result |= aSYSTEM;
171         
172         if (MAP_HIDDEN(conn) && ((sbuf->st_mode & S_IXOTH) != 0))
173                 result |= aHIDDEN;   
174   
175         if (S_ISDIR(sbuf->st_mode))
176                 result = aDIR | (result & aRONLY);
177
178         result |= set_sparse_flag(sbuf);
179  
180 #ifdef S_ISLNK
181 #if LINKS_READ_ONLY
182         if (S_ISLNK(sbuf->st_mode) && S_ISDIR(sbuf->st_mode))
183                 result |= aRONLY;
184 #endif
185 #endif
186
187         DEBUG(8,("dos_mode_from_sbuf returning "));
188
189         if (result & aHIDDEN) DEBUG(8, ("h"));
190         if (result & aRONLY ) DEBUG(8, ("r"));
191         if (result & aSYSTEM) DEBUG(8, ("s"));
192         if (result & aDIR   ) DEBUG(8, ("d"));
193         if (result & aARCH  ) DEBUG(8, ("a"));
194         
195         DEBUG(8,("\n"));
196         return result;
197 }
198
199 /****************************************************************************
200  Get DOS attributes from an EA.
201 ****************************************************************************/
202
203 static bool get_ea_dos_attribute(connection_struct *conn, const char *path,SMB_STRUCT_STAT *sbuf, uint32 *pattr)
204 {
205         ssize_t sizeret;
206         fstring attrstr;
207         unsigned int dosattr;
208
209         if (!lp_store_dos_attributes(SNUM(conn))) {
210                 return False;
211         }
212
213         /* Don't reset pattr to zero as we may already have filename-based attributes we
214            need to preserve. */
215
216         sizeret = SMB_VFS_GETXATTR(conn, path, SAMBA_XATTR_DOS_ATTRIB, attrstr, sizeof(attrstr));
217         if (sizeret == -1) {
218 #if defined(ENOTSUP) && defined(ENOATTR)
219                 if ((errno != ENOTSUP) && (errno != ENOATTR) && (errno != EACCES) && (errno != EPERM)) {
220                         DEBUG(1,("get_ea_dos_attributes: Cannot get attribute from EA on file %s: Error = %s\n",
221                                 path, strerror(errno) ));
222                         set_store_dos_attributes(SNUM(conn), False);
223                 }
224 #endif
225                 return False;
226         }
227         /* Null terminate string. */
228         attrstr[sizeret] = 0;
229         DEBUG(10,("get_ea_dos_attribute: %s attrstr = %s\n", path, attrstr));
230
231         if (sizeret < 2 || attrstr[0] != '0' || attrstr[1] != 'x' ||
232                         sscanf(attrstr, "%x", &dosattr) != 1) {
233                 DEBUG(1,("get_ea_dos_attributes: Badly formed DOSATTRIB on file %s - %s\n", path, attrstr));
234                 return False;
235         }
236
237         if (S_ISDIR(sbuf->st_mode)) {
238                 dosattr |= aDIR;
239         }
240         *pattr = (uint32)(dosattr & SAMBA_ATTRIBUTES_MASK);
241
242         DEBUG(8,("get_ea_dos_attribute returning (0x%x)", dosattr));
243
244         if (dosattr & aHIDDEN) DEBUG(8, ("h"));
245         if (dosattr & aRONLY ) DEBUG(8, ("r"));
246         if (dosattr & aSYSTEM) DEBUG(8, ("s"));
247         if (dosattr & aDIR   ) DEBUG(8, ("d"));
248         if (dosattr & aARCH  ) DEBUG(8, ("a"));
249         
250         DEBUG(8,("\n"));
251
252         return True;
253 }
254
255 /****************************************************************************
256  Set DOS attributes in an EA.
257 ****************************************************************************/
258
259 static bool set_ea_dos_attribute(connection_struct *conn, const char *path, SMB_STRUCT_STAT *sbuf, uint32 dosmode)
260 {
261         fstring attrstr;
262         files_struct *fsp = NULL;
263         bool ret = False;
264
265         if (!lp_store_dos_attributes(SNUM(conn))) {
266                 return False;
267         }
268
269         snprintf(attrstr, sizeof(attrstr)-1, "0x%x", dosmode & SAMBA_ATTRIBUTES_MASK);
270         if (SMB_VFS_SETXATTR(conn, path, SAMBA_XATTR_DOS_ATTRIB, attrstr, strlen(attrstr), 0) == -1) {
271                 if((errno != EPERM) && (errno != EACCES)) {
272                         if (errno == ENOSYS
273 #if defined(ENOTSUP)
274                                 || errno == ENOTSUP) {
275 #else
276                                 ) {
277 #endif
278                                 set_store_dos_attributes(SNUM(conn), False);
279                         }
280                         return False;
281                 }
282
283                 /* We want DOS semantics, ie allow non owner with write permission to change the
284                         bits on a file. Just like file_ntimes below.
285                 */
286
287                 /* Check if we have write access. */
288                 if(!CAN_WRITE(conn) || !lp_dos_filemode(SNUM(conn)))
289                         return False;
290
291                 /*
292                  * We need to open the file with write access whilst
293                  * still in our current user context. This ensures we
294                  * are not violating security in doing the setxattr.
295                  */
296
297                 if (!NT_STATUS_IS_OK(open_file_fchmod(conn,path,sbuf,&fsp)))
298                         return ret;
299                 become_root();
300                 if (SMB_VFS_SETXATTR(conn, path, SAMBA_XATTR_DOS_ATTRIB, attrstr, strlen(attrstr), 0) == 0) {
301                         ret = True;
302                 }
303                 unbecome_root();
304                 close_file_fchmod(fsp);
305                 return ret;
306         }
307         DEBUG(10,("set_ea_dos_attribute: set EA %s on file %s\n", attrstr, path));
308         return True;
309 }
310
311 /****************************************************************************
312  Change a unix mode to a dos mode for an ms dfs link.
313 ****************************************************************************/
314
315 uint32 dos_mode_msdfs(connection_struct *conn, const char *path,SMB_STRUCT_STAT *sbuf)
316 {
317         uint32 result = 0;
318
319         DEBUG(8,("dos_mode_msdfs: %s\n", path));
320
321         if (!VALID_STAT(*sbuf)) {
322                 return 0;
323         }
324
325         /* First do any modifications that depend on the path name. */
326         /* hide files with a name starting with a . */
327         if (lp_hide_dot_files(SNUM(conn))) {
328                 const char *p = strrchr_m(path,'/');
329                 if (p) {
330                         p++;
331                 } else {
332                         p = path;
333                 }
334                 
335                 if (p[0] == '.' && p[1] != '.' && p[1] != 0) {
336                         result |= aHIDDEN;
337                 }
338         }
339         
340         result |= dos_mode_from_sbuf(conn, path, sbuf);
341
342         /* Optimization : Only call is_hidden_path if it's not already
343            hidden. */
344         if (!(result & aHIDDEN) && IS_HIDDEN_PATH(conn,path)) {
345                 result |= aHIDDEN;
346         }
347
348         DEBUG(8,("dos_mode_msdfs returning "));
349
350         if (result & aHIDDEN) DEBUG(8, ("h"));
351         if (result & aRONLY ) DEBUG(8, ("r"));
352         if (result & aSYSTEM) DEBUG(8, ("s"));
353         if (result & aDIR   ) DEBUG(8, ("d"));
354         if (result & aARCH  ) DEBUG(8, ("a"));
355         if (result & FILE_ATTRIBUTE_SPARSE ) DEBUG(8, ("[sparse]"));
356         
357         DEBUG(8,("\n"));
358
359         return(result);
360 }
361
362 /****************************************************************************
363  Change a unix mode to a dos mode.
364 ****************************************************************************/
365
366 uint32 dos_mode(connection_struct *conn, const char *path,SMB_STRUCT_STAT *sbuf)
367 {
368         uint32 result = 0;
369
370         DEBUG(8,("dos_mode: %s\n", path));
371
372         if (!VALID_STAT(*sbuf)) {
373                 return 0;
374         }
375
376         /* First do any modifications that depend on the path name. */
377         /* hide files with a name starting with a . */
378         if (lp_hide_dot_files(SNUM(conn))) {
379                 const char *p = strrchr_m(path,'/');
380                 if (p) {
381                         p++;
382                 } else {
383                         p = path;
384                 }
385                 
386                 if (p[0] == '.' && p[1] != '.' && p[1] != 0) {
387                         result |= aHIDDEN;
388                 }
389         }
390         
391         /* Get the DOS attributes from an EA by preference. */
392         if (get_ea_dos_attribute(conn, path, sbuf, &result)) {
393                 result |= set_sparse_flag(sbuf);
394         } else {
395                 result |= dos_mode_from_sbuf(conn, path, sbuf);
396         }
397
398         if (S_ISREG(sbuf->st_mode)) {
399                 result |= set_offline_flag(conn, path);
400         }
401
402         /* Optimization : Only call is_hidden_path if it's not already
403            hidden. */
404         if (!(result & aHIDDEN) && IS_HIDDEN_PATH(conn,path)) {
405                 result |= aHIDDEN;
406         }
407
408         DEBUG(8,("dos_mode returning "));
409
410         if (result & aHIDDEN) DEBUG(8, ("h"));
411         if (result & aRONLY ) DEBUG(8, ("r"));
412         if (result & aSYSTEM) DEBUG(8, ("s"));
413         if (result & aDIR   ) DEBUG(8, ("d"));
414         if (result & aARCH  ) DEBUG(8, ("a"));
415         if (result & FILE_ATTRIBUTE_SPARSE ) DEBUG(8, ("[sparse]"));
416         
417         DEBUG(8,("\n"));
418
419         return(result);
420 }
421
422 /*******************************************************************
423  chmod a file - but preserve some bits.
424 ********************************************************************/
425
426 int file_set_dosmode(connection_struct *conn, const char *fname,
427                      uint32 dosmode, SMB_STRUCT_STAT *st,
428                      const char *parent_dir,
429                      bool newfile)
430 {
431         SMB_STRUCT_STAT st1;
432         int mask=0;
433         mode_t tmp;
434         mode_t unixmode;
435         int ret = -1;
436
437         /* We only allow READONLY|HIDDEN|SYSTEM|DIRECTORY|ARCHIVE here. */
438         dosmode &= SAMBA_ATTRIBUTES_MASK;
439
440         DEBUG(10,("file_set_dosmode: setting dos mode 0x%x on file %s\n", dosmode, fname));
441         if (!st || (st && !VALID_STAT(*st))) {
442                 st = &st1;
443                 if (SMB_VFS_STAT(conn,fname,st))
444                         return(-1);
445         }
446
447         get_acl_group_bits(conn, fname, &st->st_mode);
448
449         if (S_ISDIR(st->st_mode))
450                 dosmode |= aDIR;
451         else
452                 dosmode &= ~aDIR;
453
454         if (dos_mode(conn,fname,st) == dosmode)
455                 return(0);
456
457         /* Store the DOS attributes in an EA by preference. */
458         if (set_ea_dos_attribute(conn, fname, st, dosmode)) {
459                 if (!newfile) {
460                         notify_fname(conn, NOTIFY_ACTION_MODIFIED,
461                                 FILE_NOTIFY_CHANGE_ATTRIBUTES, fname);
462                 }
463                 return 0;
464         }
465
466         unixmode = unix_mode(conn,dosmode,fname, parent_dir);
467
468         /* preserve the s bits */
469         mask |= (S_ISUID | S_ISGID);
470
471         /* preserve the t bit */
472 #ifdef S_ISVTX
473         mask |= S_ISVTX;
474 #endif
475
476         /* possibly preserve the x bits */
477         if (!MAP_ARCHIVE(conn))
478                 mask |= S_IXUSR;
479         if (!MAP_SYSTEM(conn))
480                 mask |= S_IXGRP;
481         if (!MAP_HIDDEN(conn))
482                 mask |= S_IXOTH;
483
484         unixmode |= (st->st_mode & mask);
485
486         /* if we previously had any r bits set then leave them alone */
487         if ((tmp = st->st_mode & (S_IRUSR|S_IRGRP|S_IROTH))) {
488                 unixmode &= ~(S_IRUSR|S_IRGRP|S_IROTH);
489                 unixmode |= tmp;
490         }
491
492         /* if we previously had any w bits set then leave them alone 
493                 whilst adding in the new w bits, if the new mode is not rdonly */
494         if (!IS_DOS_READONLY(dosmode)) {
495                 unixmode |= (st->st_mode & (S_IWUSR|S_IWGRP|S_IWOTH));
496         }
497
498         if ((ret = SMB_VFS_CHMOD(conn,fname,unixmode)) == 0) {
499                 if (!newfile) {
500                         notify_fname(conn, NOTIFY_ACTION_MODIFIED,
501                                 FILE_NOTIFY_CHANGE_ATTRIBUTES, fname);
502                 }
503                 return 0;
504         }
505
506         if((errno != EPERM) && (errno != EACCES))
507                 return -1;
508
509         if(!lp_dos_filemode(SNUM(conn)))
510                 return -1;
511
512         /* We want DOS semantics, ie allow non owner with write permission to change the
513                 bits on a file. Just like file_ntimes below.
514         */
515
516         /* Check if we have write access. */
517         if (CAN_WRITE(conn)) {
518                 /*
519                  * We need to open the file with write access whilst
520                  * still in our current user context. This ensures we
521                  * are not violating security in doing the fchmod.
522                  * This file open does *not* break any oplocks we are
523                  * holding. We need to review this.... may need to
524                  * break batch oplocks open by others. JRA.
525                  */
526                 files_struct *fsp;
527                 if (!NT_STATUS_IS_OK(open_file_fchmod(conn,fname,st,&fsp)))
528                         return -1;
529                 become_root();
530                 ret = SMB_VFS_FCHMOD(fsp, fsp->fh->fd, unixmode);
531                 unbecome_root();
532                 close_file_fchmod(fsp);
533                 if (!newfile) {
534                         notify_fname(conn, NOTIFY_ACTION_MODIFIED,
535                                 FILE_NOTIFY_CHANGE_ATTRIBUTES, fname);
536                 }
537         }
538
539         return( ret );
540 }
541
542 /*******************************************************************
543  Wrapper around the VFS ntimes that possibly allows DOS semantics rather
544  than POSIX.
545 *******************************************************************/
546
547 int file_ntimes(connection_struct *conn, const char *fname, const struct timespec ts[2])
548 {
549         SMB_STRUCT_STAT sbuf;
550         int ret = -1;
551
552         errno = 0;
553         ZERO_STRUCT(sbuf);
554
555         /* Don't update the time on read-only shares */
556         /* We need this as set_filetime (which can be called on
557            close and other paths) can end up calling this function
558            without the NEED_WRITE protection. Found by : 
559            Leo Weppelman <leo@wau.mis.ah.nl>
560         */
561
562         if (!CAN_WRITE(conn)) {
563                 return 0;
564         }
565
566         if(SMB_VFS_NTIMES(conn, fname, ts) == 0) {
567                 return 0;
568         }
569
570         if((errno != EPERM) && (errno != EACCES)) {
571                 return -1;
572         }
573
574         if(!lp_dos_filetimes(SNUM(conn))) {
575                 return -1;
576         }
577
578         /* We have permission (given by the Samba admin) to
579            break POSIX semantics and allow a user to change
580            the time on a file they don't own but can write to
581            (as DOS does).
582          */
583
584         /* Check if we have write access. */
585         if (can_write_to_file(conn, fname, &sbuf)) {
586                 /* We are allowed to become root and change the filetime. */
587                 become_root();
588                 ret = SMB_VFS_NTIMES(conn, fname, ts);
589                 unbecome_root();
590         }
591
592         return ret;
593 }
594
595 /*******************************************************************
596  Change a filetime - possibly allowing DOS semantics.
597 *******************************************************************/
598
599 bool set_filetime(connection_struct *conn, const char *fname,
600                 const struct timespec mtime)
601 {
602         struct timespec ts[2];
603
604         if (null_timespec(mtime)) {
605                 return(True);
606         }
607
608         ts[1] = mtime; /* mtime. */
609         ts[0] = ts[1]; /* atime. */
610
611         if (file_ntimes(conn, fname, ts)) {
612                 DEBUG(4,("set_filetime(%s) failed: %s\n",
613                                         fname,strerror(errno)));
614                 return False;
615         }
616
617         notify_fname(conn, NOTIFY_ACTION_MODIFIED,
618                 FILE_NOTIFY_CHANGE_LAST_WRITE, fname);
619
620         return true;
621 }