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