Tidy up treatment of ctime.
[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    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_ex_size > sbuf->st_ex_blocks * (SMB_OFF_T)STAT_ST_BLOCKSIZE) {
27                 return FILE_ATTRIBUTE_SPARSE;
28         }
29 #endif
30         return 0;
31 }
32
33 static int set_link_read_only_flag(const SMB_STRUCT_STAT *const sbuf)
34 {
35 #ifdef S_ISLNK
36 #if LINKS_READ_ONLY
37         if (S_ISLNK(sbuf->st_mode) && S_ISDIR(sbuf->st_mode))
38                 return aRONLY;
39 #endif
40 #endif
41         return 0;
42 }
43
44 /****************************************************************************
45  Change a dos mode to a unix mode.
46     Base permission for files:
47          if creating file and inheriting (i.e. parent_dir != NULL)
48            apply read/write bits from parent directory.
49          else   
50            everybody gets read bit set
51          dos readonly is represented in unix by removing everyone's write bit
52          dos archive is represented in unix by the user's execute bit
53          dos system is represented in unix by the group's execute bit
54          dos hidden is represented in unix by the other's execute bit
55          if !inheriting {
56            Then apply create mask,
57            then add force bits.
58          }
59     Base permission for directories:
60          dos directory is represented in unix by unix's dir bit and the exec bit
61          if !inheriting {
62            Then apply create mask,
63            then add force bits.
64          }
65 ****************************************************************************/
66
67 mode_t unix_mode(connection_struct *conn, int dosmode,
68                  const struct smb_filename *smb_fname,
69                  const char *inherit_from_dir)
70 {
71         mode_t result = (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH);
72         mode_t dir_mode = 0; /* Mode of the inherit_from directory if
73                               * inheriting. */
74
75         if (!lp_store_dos_attributes(SNUM(conn)) && IS_DOS_READONLY(dosmode)) {
76                 result &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
77         }
78
79         if ((inherit_from_dir != NULL) && lp_inherit_perms(SNUM(conn))) {
80                 struct smb_filename *smb_fname_parent = NULL;
81                 NTSTATUS status;
82
83                 DEBUG(2, ("unix_mode(%s) inheriting from %s\n",
84                           smb_fname_str_dbg(smb_fname),
85                           inherit_from_dir));
86
87                 status = create_synthetic_smb_fname(talloc_tos(),
88                                                     inherit_from_dir, NULL,
89                                                     NULL, &smb_fname_parent);
90                 if (!NT_STATUS_IS_OK(status)) {
91                         DEBUG(1,("unix_mode(%s) failed, [dir %s]: %s\n",
92                                  smb_fname_str_dbg(smb_fname),
93                                  inherit_from_dir, nt_errstr(status)));
94                         return(0);
95                 }
96
97                 if (SMB_VFS_STAT(conn, smb_fname_parent) != 0) {
98                         DEBUG(4,("unix_mode(%s) failed, [dir %s]: %s\n",
99                                  smb_fname_str_dbg(smb_fname),
100                                  inherit_from_dir, strerror(errno)));
101                         TALLOC_FREE(smb_fname_parent);
102                         return(0);      /* *** shouldn't happen! *** */
103                 }
104
105                 /* Save for later - but explicitly remove setuid bit for safety. */
106                 dir_mode = smb_fname_parent->st.st_ex_mode & ~S_ISUID;
107                 DEBUG(2,("unix_mode(%s) inherit mode %o\n",
108                          smb_fname_str_dbg(smb_fname), (int)dir_mode));
109                 /* Clear "result" */
110                 result = 0;
111                 TALLOC_FREE(smb_fname_parent);
112         } 
113
114         if (IS_DOS_DIR(dosmode)) {
115                 /* We never make directories read only for the owner as under DOS a user
116                 can always create a file in a read-only directory. */
117                 result |= (S_IFDIR | S_IWUSR);
118
119                 if (dir_mode) {
120                         /* Inherit mode of parent directory. */
121                         result |= dir_mode;
122                 } else {
123                         /* Provisionally add all 'x' bits */
124                         result |= (S_IXUSR | S_IXGRP | S_IXOTH);                 
125
126                         /* Apply directory mask */
127                         result &= lp_dir_mask(SNUM(conn));
128                         /* Add in force bits */
129                         result |= lp_force_dir_mode(SNUM(conn));
130                 }
131         } else { 
132                 if (lp_map_archive(SNUM(conn)) && IS_DOS_ARCHIVE(dosmode))
133                         result |= S_IXUSR;
134
135                 if (lp_map_system(SNUM(conn)) && IS_DOS_SYSTEM(dosmode))
136                         result |= S_IXGRP;
137
138                 if (lp_map_hidden(SNUM(conn)) && IS_DOS_HIDDEN(dosmode))
139                         result |= S_IXOTH;  
140
141                 if (dir_mode) {
142                         /* Inherit 666 component of parent directory mode */
143                         result |= dir_mode & (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH);
144                 } else {
145                         /* Apply mode mask */
146                         result &= lp_create_mask(SNUM(conn));
147                         /* Add in force bits */
148                         result |= lp_force_create_mode(SNUM(conn));
149                 }
150         }
151
152         DEBUG(3,("unix_mode(%s) returning 0%o\n", smb_fname_str_dbg(smb_fname),
153                  (int)result));
154         return(result);
155 }
156
157 /****************************************************************************
158  Change a unix mode to a dos mode.
159 ****************************************************************************/
160
161 static uint32 dos_mode_from_sbuf(connection_struct *conn,
162                                  const struct smb_filename *smb_fname)
163 {
164         int result = 0;
165         enum mapreadonly_options ro_opts = (enum mapreadonly_options)lp_map_readonly(SNUM(conn));
166
167         if (ro_opts == MAP_READONLY_YES) {
168                 /* Original Samba method - map inverse of user "w" bit. */
169                 if ((smb_fname->st.st_ex_mode & S_IWUSR) == 0) {
170                         result |= aRONLY;
171                 }
172         } else if (ro_opts == MAP_READONLY_PERMISSIONS) {
173                 /* Check actual permissions for read-only. */
174                 if (!can_write_to_file(conn, smb_fname)) {
175                         result |= aRONLY;
176                 }
177         } /* Else never set the readonly bit. */
178
179         if (MAP_ARCHIVE(conn) && ((smb_fname->st.st_ex_mode & S_IXUSR) != 0))
180                 result |= aARCH;
181
182         if (MAP_SYSTEM(conn) && ((smb_fname->st.st_ex_mode & S_IXGRP) != 0))
183                 result |= aSYSTEM;
184
185         if (MAP_HIDDEN(conn) && ((smb_fname->st.st_ex_mode & S_IXOTH) != 0))
186                 result |= aHIDDEN;   
187
188         if (S_ISDIR(smb_fname->st.st_ex_mode))
189                 result = aDIR | (result & aRONLY);
190
191         result |= set_sparse_flag(&smb_fname->st);
192         result |= set_link_read_only_flag(&smb_fname->st);
193
194         DEBUG(8,("dos_mode_from_sbuf returning "));
195
196         if (result & aHIDDEN) DEBUG(8, ("h"));
197         if (result & aRONLY ) DEBUG(8, ("r"));
198         if (result & aSYSTEM) DEBUG(8, ("s"));
199         if (result & aDIR   ) DEBUG(8, ("d"));
200         if (result & aARCH  ) DEBUG(8, ("a"));
201
202         DEBUG(8,("\n"));
203         return result;
204 }
205
206 /****************************************************************************
207  Get DOS attributes from an EA.
208 ****************************************************************************/
209
210 static bool get_ea_dos_attribute(connection_struct *conn,
211                                  const struct smb_filename *smb_fname,
212                                  uint32 *pattr)
213 {
214         ssize_t sizeret;
215         fstring attrstr;
216         unsigned int dosattr;
217
218         if (!lp_store_dos_attributes(SNUM(conn))) {
219                 return False;
220         }
221
222         /* Don't reset pattr to zero as we may already have filename-based attributes we
223            need to preserve. */
224
225         sizeret = SMB_VFS_GETXATTR(conn, smb_fname->base_name,
226                                    SAMBA_XATTR_DOS_ATTRIB, attrstr,
227                                    sizeof(attrstr));
228         if (sizeret == -1) {
229                 if (errno == ENOSYS
230 #if defined(ENOTSUP)
231                         || errno == ENOTSUP) {
232 #else
233                                 ) {
234 #endif
235                         DEBUG(1,("get_ea_dos_attributes: Cannot get attribute "
236                                  "from EA on file %s: Error = %s\n",
237                                  smb_fname_str_dbg(smb_fname),
238                                  strerror(errno)));
239                         set_store_dos_attributes(SNUM(conn), False);
240                 }
241                 return False;
242         }
243         /* Null terminate string. */
244         attrstr[sizeret] = 0;
245         DEBUG(10,("get_ea_dos_attribute: %s attrstr = %s\n",
246                   smb_fname_str_dbg(smb_fname), attrstr));
247
248         if (sizeret < 2 || attrstr[0] != '0' || attrstr[1] != 'x' ||
249                         sscanf(attrstr, "%x", &dosattr) != 1) {
250                 DEBUG(1,("get_ea_dos_attributes: Badly formed DOSATTRIB on "
251                          "file %s - %s\n", smb_fname_str_dbg(smb_fname),
252                          attrstr));
253                 return False;
254         }
255
256         if (S_ISDIR(smb_fname->st.st_ex_mode)) {
257                 dosattr |= aDIR;
258         }
259         *pattr = (uint32)(dosattr & SAMBA_ATTRIBUTES_MASK);
260
261         DEBUG(8,("get_ea_dos_attribute returning (0x%x)", dosattr));
262
263         if (dosattr & aHIDDEN) DEBUG(8, ("h"));
264         if (dosattr & aRONLY ) DEBUG(8, ("r"));
265         if (dosattr & aSYSTEM) DEBUG(8, ("s"));
266         if (dosattr & aDIR   ) DEBUG(8, ("d"));
267         if (dosattr & aARCH  ) DEBUG(8, ("a"));
268
269         DEBUG(8,("\n"));
270
271         return True;
272 }
273
274 /****************************************************************************
275  Set DOS attributes in an EA.
276 ****************************************************************************/
277
278 static bool set_ea_dos_attribute(connection_struct *conn,
279                                  struct smb_filename *smb_fname,
280                                  uint32 dosmode)
281 {
282         fstring attrstr;
283         files_struct *fsp = NULL;
284         bool ret = False;
285
286         if (!lp_store_dos_attributes(SNUM(conn))) {
287                 return False;
288         }
289
290         snprintf(attrstr, sizeof(attrstr)-1, "0x%x", dosmode & SAMBA_ATTRIBUTES_MASK);
291         if (SMB_VFS_SETXATTR(conn, smb_fname->base_name,
292                              SAMBA_XATTR_DOS_ATTRIB, attrstr, strlen(attrstr),
293                              0) == -1) {
294                 if((errno != EPERM) && (errno != EACCES)) {
295                         if (errno == ENOSYS
296 #if defined(ENOTSUP)
297                                 || errno == ENOTSUP) {
298 #else
299                                 ) {
300 #endif
301                                 DEBUG(1,("set_ea_dos_attributes: Cannot set "
302                                          "attribute EA on file %s: Error = %s\n",
303                                          smb_fname_str_dbg(smb_fname),
304                                          strerror(errno) ));
305                                 set_store_dos_attributes(SNUM(conn), False);
306                         }
307                         return False;
308                 }
309
310                 /* We want DOS semantics, ie allow non owner with write permission to change the
311                         bits on a file. Just like file_ntimes below.
312                 */
313
314                 /* Check if we have write access. */
315                 if(!CAN_WRITE(conn) || !lp_dos_filemode(SNUM(conn)))
316                         return False;
317
318                 /*
319                  * We need to open the file with write access whilst
320                  * still in our current user context. This ensures we
321                  * are not violating security in doing the setxattr.
322                  */
323
324                 if (!NT_STATUS_IS_OK(open_file_fchmod(NULL, conn, smb_fname,
325                                                       &fsp)))
326                         return ret;
327                 become_root();
328                 if (SMB_VFS_SETXATTR(conn, smb_fname->base_name,
329                                      SAMBA_XATTR_DOS_ATTRIB, attrstr,
330                                      strlen(attrstr), 0) == 0) {
331                         ret = True;
332                 }
333                 unbecome_root();
334                 close_file_fchmod(NULL, fsp);
335                 return ret;
336         }
337         DEBUG(10,("set_ea_dos_attribute: set EA %s on file %s\n", attrstr,
338                   smb_fname_str_dbg(smb_fname)));
339         return True;
340 }
341
342 /****************************************************************************
343  Change a unix mode to a dos mode for an ms dfs link.
344 ****************************************************************************/
345
346 uint32 dos_mode_msdfs(connection_struct *conn,
347                       const struct smb_filename *smb_fname)
348 {
349         uint32 result = 0;
350
351         DEBUG(8,("dos_mode_msdfs: %s\n", smb_fname_str_dbg(smb_fname)));
352
353         if (!VALID_STAT(smb_fname->st)) {
354                 return 0;
355         }
356
357         /* First do any modifications that depend on the path name. */
358         /* hide files with a name starting with a . */
359         if (lp_hide_dot_files(SNUM(conn))) {
360                 const char *p = strrchr_m(smb_fname->base_name, '/');
361                 if (p) {
362                         p++;
363                 } else {
364                         p = smb_fname->base_name;
365                 }
366
367                 /* Only . and .. are not hidden. */
368                 if (p[0] == '.' && !((p[1] == '\0') ||
369                                 (p[1] == '.' && p[2] == '\0'))) {
370                         result |= aHIDDEN;
371                 }
372         }
373
374         result |= dos_mode_from_sbuf(conn, smb_fname);
375
376         /* Optimization : Only call is_hidden_path if it's not already
377            hidden. */
378         if (!(result & aHIDDEN) &&
379             IS_HIDDEN_PATH(conn, smb_fname->base_name)) {
380                 result |= aHIDDEN;
381         }
382
383         DEBUG(8,("dos_mode_msdfs returning "));
384
385         if (result & aHIDDEN) DEBUG(8, ("h"));
386         if (result & aRONLY ) DEBUG(8, ("r"));
387         if (result & aSYSTEM) DEBUG(8, ("s"));
388         if (result & aDIR   ) DEBUG(8, ("d"));
389         if (result & aARCH  ) DEBUG(8, ("a"));
390         if (result & FILE_ATTRIBUTE_SPARSE ) DEBUG(8, ("[sparse]"));
391
392         DEBUG(8,("\n"));
393
394         return(result);
395 }
396
397 #ifdef HAVE_STAT_DOS_FLAGS
398 /****************************************************************************
399  Convert dos attributes (FILE_ATTRIBUTE_*) to dos stat flags (UF_*)
400 ****************************************************************************/
401
402 int dos_attributes_to_stat_dos_flags(uint32_t dosmode)
403 {
404         uint32_t dos_stat_flags = 0;
405
406         if (dosmode & aARCH)
407                 dos_stat_flags |= UF_DOS_ARCHIVE;
408         if (dosmode & aHIDDEN)
409                 dos_stat_flags |= UF_DOS_HIDDEN;
410         if (dosmode & aRONLY)
411                 dos_stat_flags |= UF_DOS_RO;
412         if (dosmode & aSYSTEM)
413                 dos_stat_flags |= UF_DOS_SYSTEM;
414         if (dosmode & FILE_ATTRIBUTE_NONINDEXED)
415                 dos_stat_flags |= UF_DOS_NOINDEX;
416
417         return dos_stat_flags;
418 }
419
420 /****************************************************************************
421  Gets DOS attributes, accessed via st_ex_flags in the stat struct.
422 ****************************************************************************/
423
424 static bool get_stat_dos_flags(connection_struct *conn,
425                                const struct smb_filename *smb_fname,
426                                uint32_t *dosmode)
427 {
428         SMB_ASSERT(VALID_STAT(smb_fname->st));
429         SMB_ASSERT(dosmode);
430
431         if (!lp_store_dos_attributes(SNUM(conn))) {
432                 return false;
433         }
434
435         DEBUG(5, ("Getting stat dos attributes for %s.\n",
436                   smb_fname_str_dbg(smb_fname)));
437
438         if (smb_fname->st.st_ex_flags & UF_DOS_ARCHIVE)
439                 *dosmode |= aARCH;
440         if (smb_fname->st.st_ex_flags & UF_DOS_HIDDEN)
441                 *dosmode |= aHIDDEN;
442         if (smb_fname->st.st_ex_flags & UF_DOS_RO)
443                 *dosmode |= aRONLY;
444         if (smb_fname->st.st_ex_flags & UF_DOS_SYSTEM)
445                 *dosmode |= aSYSTEM;
446         if (smb_fname->st.st_ex_flags & UF_DOS_NOINDEX)
447                 *dosmode |= FILE_ATTRIBUTE_NONINDEXED;
448         if (S_ISDIR(smb_fname->st.st_ex_mode))
449                 *dosmode |= aDIR;
450
451         *dosmode |= set_sparse_flag(&smb_fname->st);
452         *dosmode |= set_link_read_only_flag(&smb_fname->st);
453
454         return true;
455 }
456
457 /****************************************************************************
458  Sets DOS attributes, stored in st_ex_flags of the inode.
459 ****************************************************************************/
460
461 static bool set_stat_dos_flags(connection_struct *conn,
462                                const struct smb_filename *smb_fname,
463                                uint32_t dosmode,
464                                bool *attributes_changed)
465 {
466         uint32_t new_flags = 0;
467         int error = 0;
468
469         SMB_ASSERT(VALID_STAT(smb_fname->st));
470         SMB_ASSERT(attributes_changed);
471
472         *attributes_changed = false;
473
474         if (!lp_store_dos_attributes(SNUM(conn))) {
475                 return false;
476         }
477
478         DEBUG(5, ("Setting stat dos attributes for %s.\n",
479                   smb_fname_str_dbg(smb_fname)));
480
481         new_flags = (smb_fname->st.st_ex_flags & ~UF_DOS_FLAGS) |
482                      dos_attributes_to_stat_dos_flags(dosmode);
483
484         /* Return early if no flags changed. */
485         if (new_flags == smb_fname->st.st_ex_flags)
486                 return true;
487
488         DEBUG(5, ("Setting stat dos attributes=0x%x, prev=0x%x\n", new_flags,
489                   smb_fname->st.st_ex_flags));
490
491         /* Set new flags with chflags. */
492         error = SMB_VFS_CHFLAGS(conn, smb_fname->base_name, new_flags);
493         if (error) {
494                 DEBUG(0, ("Failed setting new stat dos attributes (0x%x) on "
495                           "file %s! errno=%d\n", new_flags,
496                           smb_fname_str_dbg(smb_fname), errno));
497                 return false;
498         }
499
500         *attributes_changed = true;
501         return true;
502 }
503 #endif /* HAVE_STAT_DOS_FLAGS */
504
505 /****************************************************************************
506  Change a unix mode to a dos mode.
507 ****************************************************************************/
508
509 uint32 dos_mode(connection_struct *conn, const struct smb_filename *smb_fname)
510 {
511         SMB_STRUCT_STAT sbuf;
512         uint32 result = 0;
513         bool offline, used_stat_dos_flags = false;
514
515         DEBUG(8,("dos_mode: %s\n", smb_fname_str_dbg(smb_fname)));
516
517         if (!VALID_STAT(smb_fname->st)) {
518                 return 0;
519         }
520
521         /* First do any modifications that depend on the path name. */
522         /* hide files with a name starting with a . */
523         if (lp_hide_dot_files(SNUM(conn))) {
524                 const char *p = strrchr_m(smb_fname->base_name,'/');
525                 if (p) {
526                         p++;
527                 } else {
528                         p = smb_fname->base_name;
529                 }
530
531                 /* Only . and .. are not hidden. */
532                 if (p[0] == '.' && !((p[1] == '\0') ||
533                                 (p[1] == '.' && p[2] == '\0'))) {
534                         result |= aHIDDEN;
535                 }
536         }
537
538 #ifdef HAVE_STAT_DOS_FLAGS
539         used_stat_dos_flags = get_stat_dos_flags(conn, smb_fname, &result);
540 #endif
541         if (!used_stat_dos_flags) {
542                 /* Get the DOS attributes from an EA by preference. */
543                 if (get_ea_dos_attribute(conn, smb_fname, &result)) {
544                         result |= set_sparse_flag(&smb_fname->st);
545                 } else {
546                         result |= dos_mode_from_sbuf(conn, smb_fname);
547                 }
548         }
549
550         sbuf = smb_fname->st;
551         offline = SMB_VFS_IS_OFFLINE(conn, smb_fname->base_name, &sbuf);
552         if (S_ISREG(sbuf.st_ex_mode) && offline) {
553                 result |= FILE_ATTRIBUTE_OFFLINE;
554         }
555
556         /* Optimization : Only call is_hidden_path if it's not already
557            hidden. */
558         if (!(result & aHIDDEN) &&
559             IS_HIDDEN_PATH(conn, smb_fname->base_name)) {
560                 result |= aHIDDEN;
561         }
562
563         DEBUG(8,("dos_mode returning "));
564
565         if (result & aHIDDEN) DEBUG(8, ("h"));
566         if (result & aRONLY ) DEBUG(8, ("r"));
567         if (result & aSYSTEM) DEBUG(8, ("s"));
568         if (result & aDIR   ) DEBUG(8, ("d"));
569         if (result & aARCH  ) DEBUG(8, ("a"));
570         if (result & FILE_ATTRIBUTE_SPARSE ) DEBUG(8, ("[sparse]"));
571
572         DEBUG(8,("\n"));
573
574         return(result);
575 }
576
577 /*******************************************************************
578  chmod a file - but preserve some bits.
579 ********************************************************************/
580
581 int file_set_dosmode(connection_struct *conn, struct smb_filename *smb_fname,
582                      uint32 dosmode, const char *parent_dir, bool newfile)
583 {
584         int mask=0;
585         mode_t tmp;
586         mode_t unixmode;
587         int ret = -1, lret = -1;
588         uint32_t old_mode;
589
590         /* We only allow READONLY|HIDDEN|SYSTEM|DIRECTORY|ARCHIVE here. */
591         dosmode &= (SAMBA_ATTRIBUTES_MASK | FILE_ATTRIBUTE_OFFLINE);
592
593         DEBUG(10,("file_set_dosmode: setting dos mode 0x%x on file %s\n",
594                   dosmode, smb_fname_str_dbg(smb_fname)));
595
596         if (!VALID_STAT(smb_fname->st)) {
597                 if (SMB_VFS_STAT(conn, smb_fname))
598                         return(-1);
599         }
600
601         unixmode = smb_fname->st.st_ex_mode;
602
603         get_acl_group_bits(conn, smb_fname->base_name,
604                            &smb_fname->st.st_ex_mode);
605
606         if (S_ISDIR(smb_fname->st.st_ex_mode))
607                 dosmode |= aDIR;
608         else
609                 dosmode &= ~aDIR;
610
611         old_mode = dos_mode(conn, smb_fname);
612
613         if (dosmode & FILE_ATTRIBUTE_OFFLINE) {
614                 if (!(old_mode & FILE_ATTRIBUTE_OFFLINE)) {
615                         lret = SMB_VFS_SET_OFFLINE(conn, smb_fname->base_name);
616                         if (lret == -1) {
617                                 DEBUG(0, ("set_dos_mode: client has asked to "
618                                           "set FILE_ATTRIBUTE_OFFLINE to "
619                                           "%s/%s but there was an error while "
620                                           "setting it or it is not "
621                                           "supported.\n", parent_dir,
622                                           smb_fname_str_dbg(smb_fname)));
623                         }
624                 }
625         }
626
627         dosmode  &= ~FILE_ATTRIBUTE_OFFLINE;
628         old_mode &= ~FILE_ATTRIBUTE_OFFLINE;
629
630         if (old_mode == dosmode) {
631                 smb_fname->st.st_ex_mode = unixmode;
632                 return(0);
633         }
634
635 #ifdef HAVE_STAT_DOS_FLAGS
636         {
637                 bool attributes_changed;
638
639                 if (set_stat_dos_flags(conn, smb_fname, dosmode,
640                                        &attributes_changed))
641                 {
642                         if (!newfile && attributes_changed) {
643                                 notify_fname(conn, NOTIFY_ACTION_MODIFIED,
644                                     FILE_NOTIFY_CHANGE_ATTRIBUTES,
645                                     smb_fname->base_name);
646                         }
647                         smb_fname->st.st_ex_mode = unixmode;
648                         return 0;
649                 }
650         }
651 #endif
652         /* Store the DOS attributes in an EA by preference. */
653         if (set_ea_dos_attribute(conn, smb_fname, dosmode)) {
654                 if (!newfile) {
655                         notify_fname(conn, NOTIFY_ACTION_MODIFIED,
656                                      FILE_NOTIFY_CHANGE_ATTRIBUTES,
657                                      smb_fname->base_name);
658                 }
659                 smb_fname->st.st_ex_mode = unixmode;
660                 return 0;
661         }
662
663         unixmode = unix_mode(conn, dosmode, smb_fname, parent_dir);
664
665         /* preserve the s bits */
666         mask |= (S_ISUID | S_ISGID);
667
668         /* preserve the t bit */
669 #ifdef S_ISVTX
670         mask |= S_ISVTX;
671 #endif
672
673         /* possibly preserve the x bits */
674         if (!MAP_ARCHIVE(conn))
675                 mask |= S_IXUSR;
676         if (!MAP_SYSTEM(conn))
677                 mask |= S_IXGRP;
678         if (!MAP_HIDDEN(conn))
679                 mask |= S_IXOTH;
680
681         unixmode |= (smb_fname->st.st_ex_mode & mask);
682
683         /* if we previously had any r bits set then leave them alone */
684         if ((tmp = smb_fname->st.st_ex_mode & (S_IRUSR|S_IRGRP|S_IROTH))) {
685                 unixmode &= ~(S_IRUSR|S_IRGRP|S_IROTH);
686                 unixmode |= tmp;
687         }
688
689         /* if we previously had any w bits set then leave them alone 
690                 whilst adding in the new w bits, if the new mode is not rdonly */
691         if (!IS_DOS_READONLY(dosmode)) {
692                 unixmode |= (smb_fname->st.st_ex_mode & (S_IWUSR|S_IWGRP|S_IWOTH));
693         }
694
695         ret = SMB_VFS_CHMOD(conn, smb_fname->base_name, unixmode);
696         if (ret == 0) {
697                 if(!newfile || (lret != -1)) {
698                         notify_fname(conn, NOTIFY_ACTION_MODIFIED,
699                                      FILE_NOTIFY_CHANGE_ATTRIBUTES,
700                                      smb_fname->base_name);
701                 }
702                 smb_fname->st.st_ex_mode = unixmode;
703                 return 0;
704         }
705
706         if((errno != EPERM) && (errno != EACCES))
707                 return -1;
708
709         if(!lp_dos_filemode(SNUM(conn)))
710                 return -1;
711
712         /* We want DOS semantics, ie allow non owner with write permission to change the
713                 bits on a file. Just like file_ntimes below.
714         */
715
716         /* Check if we have write access. */
717         if (CAN_WRITE(conn)) {
718                 /*
719                  * We need to open the file with write access whilst
720                  * still in our current user context. This ensures we
721                  * are not violating security in doing the fchmod.
722                  * This file open does *not* break any oplocks we are
723                  * holding. We need to review this.... may need to
724                  * break batch oplocks open by others. JRA.
725                  */
726                 files_struct *fsp;
727                 if (!NT_STATUS_IS_OK(open_file_fchmod(NULL, conn, smb_fname,
728                                      &fsp)))
729                         return -1;
730                 become_root();
731                 ret = SMB_VFS_FCHMOD(fsp, unixmode);
732                 unbecome_root();
733                 close_file_fchmod(NULL, fsp);
734                 if (!newfile) {
735                         notify_fname(conn, NOTIFY_ACTION_MODIFIED,
736                                      FILE_NOTIFY_CHANGE_ATTRIBUTES,
737                                      smb_fname->base_name);
738                 }
739                 if (ret == 0) {
740                         smb_fname->st.st_ex_mode = unixmode;
741                 }
742         }
743
744         return( ret );
745 }
746
747 /*******************************************************************
748  Wrapper around the VFS ntimes that possibly allows DOS semantics rather
749  than POSIX.
750 *******************************************************************/
751
752 int file_ntimes(connection_struct *conn, const struct smb_filename *smb_fname,
753                 struct smb_file_time *ft)
754 {
755         int ret = -1;
756
757         errno = 0;
758
759         DEBUG(6, ("file_ntime: actime: %s",
760                   time_to_asc(convert_timespec_to_time_t(ft->atime))));
761         DEBUG(6, ("file_ntime: modtime: %s",
762                   time_to_asc(convert_timespec_to_time_t(ft->mtime))));
763         DEBUG(6, ("file_ntime: ctime: %s",
764                   time_to_asc(convert_timespec_to_time_t(ft->ctime))));
765         DEBUG(6, ("file_ntime: createtime: %s",
766                   time_to_asc(convert_timespec_to_time_t(ft->create_time))));
767
768         /* Don't update the time on read-only shares */
769         /* We need this as set_filetime (which can be called on
770            close and other paths) can end up calling this function
771            without the NEED_WRITE protection. Found by : 
772            Leo Weppelman <leo@wau.mis.ah.nl>
773         */
774
775         if (!CAN_WRITE(conn)) {
776                 return 0;
777         }
778
779         if(SMB_VFS_NTIMES(conn, smb_fname, ft) == 0) {
780                 return 0;
781         }
782
783         if((errno != EPERM) && (errno != EACCES)) {
784                 return -1;
785         }
786
787         if(!lp_dos_filetimes(SNUM(conn))) {
788                 return -1;
789         }
790
791         /* We have permission (given by the Samba admin) to
792            break POSIX semantics and allow a user to change
793            the time on a file they don't own but can write to
794            (as DOS does).
795          */
796
797         /* Check if we have write access. */
798         if (can_write_to_file(conn, smb_fname)) {
799                 /* We are allowed to become root and change the filetime. */
800                 become_root();
801                 ret = SMB_VFS_NTIMES(conn, smb_fname, ft);
802                 unbecome_root();
803         }
804
805         return ret;
806 }
807
808 /******************************************************************
809  Force a "sticky" write time on a pathname. This will always be
810  returned on all future write time queries and set on close.
811 ******************************************************************/
812
813 bool set_sticky_write_time_path(struct file_id fileid, struct timespec mtime)
814 {
815         if (null_timespec(mtime)) {
816                 return true;
817         }
818
819         if (!set_sticky_write_time(fileid, mtime)) {
820                 return false;
821         }
822
823         return true;
824 }
825
826 /******************************************************************
827  Force a "sticky" write time on an fsp. This will always be
828  returned on all future write time queries and set on close.
829 ******************************************************************/
830
831 bool set_sticky_write_time_fsp(struct files_struct *fsp, struct timespec mtime)
832 {
833         fsp->write_time_forced = true;
834         TALLOC_FREE(fsp->update_write_time_event);
835
836         return set_sticky_write_time_path(fsp->file_id, mtime);
837 }
838
839 /******************************************************************
840  Update a write time immediately, without the 2 second delay.
841 ******************************************************************/
842
843 bool update_write_time(struct files_struct *fsp)
844 {
845         if (!set_write_time(fsp->file_id, timespec_current())) {
846                 return false;
847         }
848
849         notify_fname(fsp->conn, NOTIFY_ACTION_MODIFIED,
850                      FILE_NOTIFY_CHANGE_LAST_WRITE, fsp->fsp_name->base_name);
851
852         return true;
853 }