1 /*
2    Unix SMB/Netbios implementation.
3    VFS module to get and set posix acls through xattr
4    Copyright (c) 2013 Anand Avati <avati@redhat.com>
5    Copyright (c) 2016 Yan, Zheng <zyan@redhat.com>
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 #include "system/filesys.h"
23 #include "smbd/smbd.h"
24 #include "modules/posixacl_xattr.h"
25 
26 /*
27    POSIX ACL Format:
28 
29    Size = 4 (header) + N * 8 (entry)
30 
31    Offset  Size    Field (Little Endian)
32    -------------------------------------
33    0-3     4-byte  Version
34 
35    4-5     2-byte  Entry-1 tag
36    6-7     2-byte  Entry-1 perm
37    8-11    4-byte  Entry-1 id
38 
39    12-13   2-byte  Entry-2 tag
40    14-15   2-byte  Entry-2 perm
41    16-19   4-byte  Entry-2 id
42 
43    ...
44 
45  */
46 
47 
48 
49 /* private functions */
50 
51 #define ACL_EA_ACCESS		"system.posix_acl_access"
52 #define ACL_EA_DEFAULT		"system.posix_acl_default"
53 #define ACL_EA_VERSION		0x0002
54 #define ACL_EA_HEADER_SIZE	4
55 #define ACL_EA_ENTRY_SIZE	8
56 
57 #define ACL_EA_SIZE(n)  (ACL_EA_HEADER_SIZE + ((n) * ACL_EA_ENTRY_SIZE))
58 
mode_to_smb_acl(mode_t mode,TALLOC_CTX * mem_ctx)59 static SMB_ACL_T mode_to_smb_acl(mode_t mode, TALLOC_CTX *mem_ctx)
60 {
61 	struct smb_acl_t *result;
62 	int count;
63 
64 	count = 3;
65 	result = sys_acl_init(mem_ctx);
66 	if (!result) {
67 		return NULL;
68 	}
69 
70 	result->acl = talloc_array(result, struct smb_acl_entry, count);
71 	if (!result->acl) {
72 		errno = ENOMEM;
73 		talloc_free(result);
74 		return NULL;
75 	}
76 
77 	result->count = count;
78 
79 	result->acl[0].a_type = SMB_ACL_USER_OBJ;
80 	result->acl[0].a_perm = (mode & S_IRWXU) >> 6;
81 
82 	result->acl[1].a_type = SMB_ACL_GROUP_OBJ;
83 	result->acl[1].a_perm = (mode & S_IRWXG) >> 3;
84 
85 	result->acl[2].a_type = SMB_ACL_OTHER;
86 	result->acl[2].a_perm = mode & S_IRWXO;
87 
88 	return result;
89 }
90 
posixacl_xattr_to_smb_acl(const char * buf,size_t xattr_size,TALLOC_CTX * mem_ctx)91 static SMB_ACL_T posixacl_xattr_to_smb_acl(const char *buf, size_t xattr_size,
92 					   TALLOC_CTX *mem_ctx)
93 {
94 	int count;
95 	int size;
96 	struct smb_acl_entry *smb_ace;
97 	struct smb_acl_t *result;
98 	int i;
99 	int offset;
100 	uint16_t tag;
101 	uint16_t perm;
102 	uint32_t id;
103 
104 	size = xattr_size;
105 
106 	if (size < ACL_EA_HEADER_SIZE) {
107 		/* ACL should be at least as big as the header (4 bytes) */
108 		errno = EINVAL;
109 		return NULL;
110 	}
111 
112 	/* Version is the first 4 bytes of the ACL */
113 	if (IVAL(buf, 0) != ACL_EA_VERSION) {
114 		DEBUG(0, ("Unknown ACL EA version: %d\n",
115 			  IVAL(buf, 0)));
116 		errno = EINVAL;
117 		return NULL;
118 	}
119 	offset = ACL_EA_HEADER_SIZE;
120 
121 	size -= ACL_EA_HEADER_SIZE;
122 	if (size % ACL_EA_ENTRY_SIZE) {
123 		/* Size of entries must strictly be a multiple of
124 		   size of an ACE (8 bytes)
125 		*/
126 		DEBUG(0, ("Invalid ACL EA size: %d\n", size));
127 		errno = EINVAL;
128 		return NULL;
129 	}
130 
131 	count = size / ACL_EA_ENTRY_SIZE;
132 
133 	result = sys_acl_init(mem_ctx);
134 	if (!result) {
135 		return NULL;
136 	}
137 
138 	result->acl = talloc_array(result, struct smb_acl_entry, count);
139 	if (!result->acl) {
140 		errno = ENOMEM;
141 		talloc_free(result);
142 		return NULL;
143 	}
144 
145 	result->count = count;
146 
147 	smb_ace = result->acl;
148 
149 	for (i = 0; i < count; i++) {
150 		/* TAG is the first 2 bytes of an entry */
151 		tag = SVAL(buf, offset);
152 		offset += 2;
153 
154 		/* PERM is the next 2 bytes of an entry */
155 		perm = SVAL(buf, offset);
156 		offset += 2;
157 
158 		/* ID is the last 4 bytes of an entry */
159 		id = IVAL(buf, offset);
160 		offset += 4;
161 
162 		switch(tag) {
163 		case ACL_USER:
164 			smb_ace->a_type = SMB_ACL_USER;
165 			break;
166 		case ACL_USER_OBJ:
167 			smb_ace->a_type = SMB_ACL_USER_OBJ;
168 			break;
169 		case ACL_GROUP:
170 			smb_ace->a_type = SMB_ACL_GROUP;
171 			break;
172 		case ACL_GROUP_OBJ:
173 			smb_ace->a_type = SMB_ACL_GROUP_OBJ;
174 			break;
175 		case ACL_OTHER:
176 			smb_ace->a_type = SMB_ACL_OTHER;
177 			break;
178 		case ACL_MASK:
179 			smb_ace->a_type = SMB_ACL_MASK;
180 			break;
181 		default:
182 			DEBUG(0, ("unknown tag type %d\n", (unsigned int) tag));
183 			errno = EINVAL;
184 			return NULL;
185 		}
186 
187 
188 		switch(smb_ace->a_type) {
189 		case SMB_ACL_USER:
190 			smb_ace->info.user.uid = id;
191 			break;
192 		case SMB_ACL_GROUP:
193 			smb_ace->info.group.gid = id;
194 			break;
195 		default:
196 			break;
197 		}
198 
199 		smb_ace->a_perm = 0;
200 		smb_ace->a_perm |= ((perm & ACL_READ) ? SMB_ACL_READ : 0);
201 		smb_ace->a_perm |= ((perm & ACL_WRITE) ? SMB_ACL_WRITE : 0);
202 		smb_ace->a_perm |= ((perm & ACL_EXECUTE) ? SMB_ACL_EXECUTE : 0);
203 
204 		smb_ace++;
205 	}
206 
207 	return result;
208 }
209 
210 
posixacl_xattr_entry_compare(const void * left,const void * right)211 static int posixacl_xattr_entry_compare(const void *left, const void *right)
212 {
213 	int ret = 0;
214 	uint16_t tag_left, tag_right;
215 	uint32_t id_left, id_right;
216 
217 	/*
218 	  Sorting precedence:
219 	   - Smaller TAG values must be earlier.
220 	   - Within same TAG, smaller identifiers must be earlier, E.g:
221 	     UID 0 entry must be earlier than UID 200
222 	     GID 17 entry must be earlier than GID 19
223 	*/
224 
225 	/* TAG is the first element in the entry */
226 	tag_left = SVAL(left, 0);
227 	tag_right = SVAL(right, 0);
228 
229 	ret = (tag_left - tag_right);
230 	if (!ret) {
231 		/* ID is the third element in the entry, after two short
232 		   integers (tag and perm), i.e at offset 4.
233 		*/
234 		id_left = IVAL(left, 4);
235 		id_right = IVAL(right, 4);
236 		ret = id_left - id_right;
237 	}
238 
239 	return ret;
240 }
241 
242 
smb_acl_to_posixacl_xattr(SMB_ACL_T theacl,char * buf,size_t len)243 static int smb_acl_to_posixacl_xattr(SMB_ACL_T theacl, char *buf, size_t len)
244 {
245 	ssize_t size;
246 	struct smb_acl_entry *smb_ace;
247 	int i;
248 	int count;
249 	uint16_t tag;
250 	uint16_t perm;
251 	uint32_t id;
252 	int offset;
253 
254 	count = theacl->count;
255 
256 	size = ACL_EA_SIZE(count);
257 	if (!buf) {
258 		return size;
259 	}
260 	if (len < size) {
261 		return -ERANGE;
262 	}
263 	smb_ace = theacl->acl;
264 
265 	/* Version is the first 4 bytes of the ACL */
266 	SIVAL(buf, 0, ACL_EA_VERSION);
267 	offset = ACL_EA_HEADER_SIZE;
268 
269 	for (i = 0; i < count; i++) {
270 		/* Calculate tag */
271 		switch(smb_ace->a_type) {
272 		case SMB_ACL_USER:
273 			tag = ACL_USER;
274 			break;
275 		case SMB_ACL_USER_OBJ:
276 			tag = ACL_USER_OBJ;
277 			break;
278 		case SMB_ACL_GROUP:
279 			tag = ACL_GROUP;
280 			break;
281 		case SMB_ACL_GROUP_OBJ:
282 			tag = ACL_GROUP_OBJ;
283 			break;
284 		case SMB_ACL_OTHER:
285 			tag = ACL_OTHER;
286 			break;
287 		case SMB_ACL_MASK:
288 			tag = ACL_MASK;
289 			break;
290 		default:
291 			DEBUG(0, ("Unknown tag value %d\n",
292 				  smb_ace->a_type));
293 			return -EINVAL;
294 		}
295 
296 
297 		/* Calculate id */
298 		switch(smb_ace->a_type) {
299 		case SMB_ACL_USER:
300 			id = smb_ace->info.user.uid;
301 			break;
302 		case SMB_ACL_GROUP:
303 			id = smb_ace->info.group.gid;
304 			break;
305 		default:
306 			id = ACL_UNDEFINED_ID;
307 			break;
308 		}
309 
310 		/* Calculate perm */
311 		perm = 0;
312 		perm |= (smb_ace->a_perm & SMB_ACL_READ) ? ACL_READ : 0;
313 		perm |= (smb_ace->a_perm & SMB_ACL_WRITE) ? ACL_WRITE : 0;
314 		perm |= (smb_ace->a_perm & SMB_ACL_EXECUTE) ? ACL_EXECUTE : 0;
315 
316 		/* TAG is the first 2 bytes of an entry */
317 		SSVAL(buf, offset, tag);
318 		offset += 2;
319 
320 		/* PERM is the next 2 bytes of an entry */
321 		SSVAL(buf, offset, perm);
322 		offset += 2;
323 
324 		/* ID is the last 4 bytes of an entry */
325 		SIVAL(buf, offset, id);
326 		offset += 4;
327 
328 		smb_ace++;
329 	}
330 
331 	/* Skip the header, sort @count number of 8-byte entries */
332 	qsort(buf+ACL_EA_HEADER_SIZE, count, ACL_EA_ENTRY_SIZE,
333 	      posixacl_xattr_entry_compare);
334 
335 	return size;
336 }
337 
posixacl_xattr_acl_get_file(vfs_handle_struct * handle,const struct smb_filename * smb_fname,SMB_ACL_TYPE_T type,TALLOC_CTX * mem_ctx)338 SMB_ACL_T posixacl_xattr_acl_get_file(vfs_handle_struct *handle,
339 				      const struct smb_filename *smb_fname,
340 				      SMB_ACL_TYPE_T type,
341 				      TALLOC_CTX *mem_ctx)
342 {
343 	int ret;
344 	int size;
345 	char *buf;
346 	const char *name;
347 
348 	if (type == SMB_ACL_TYPE_ACCESS) {
349 		name = ACL_EA_ACCESS;
350 	} else if (type == SMB_ACL_TYPE_DEFAULT) {
351 		name = ACL_EA_DEFAULT;
352 	} else {
353 		errno = EINVAL;
354 		return NULL;
355 	}
356 
357 	size = ACL_EA_SIZE(20);
358 	buf = alloca(size);
359 	if (!buf) {
360 		return NULL;
361 	}
362 
363 	ret = SMB_VFS_GETXATTR(handle->conn, smb_fname,
364 				name, buf, size);
365 	if (ret < 0 && errno == ERANGE) {
366 		size = SMB_VFS_GETXATTR(handle->conn, smb_fname,
367 					name, NULL, 0);
368 		if (size > 0) {
369 			buf = alloca(size);
370 			if (!buf) {
371 				return NULL;
372 			}
373 			ret = SMB_VFS_GETXATTR(handle->conn,
374 						smb_fname, name,
375 						buf, size);
376 		}
377 	}
378 
379 	if (ret > 0) {
380 		return posixacl_xattr_to_smb_acl(buf, ret, mem_ctx);
381 	}
382 	if (ret == 0 || errno == ENOATTR) {
383 		mode_t mode = 0;
384 		TALLOC_CTX *frame = talloc_stackframe();
385 		struct smb_filename *smb_fname_tmp =
386 			cp_smb_filename_nostream(frame, smb_fname);
387 		if (smb_fname_tmp == NULL) {
388 			errno = ENOMEM;
389 			ret = -1;
390 		} else {
391 			ret = SMB_VFS_STAT(handle->conn, smb_fname_tmp);
392 			if (ret == 0) {
393 				mode = smb_fname_tmp->st.st_ex_mode;
394 			}
395 		}
396 		TALLOC_FREE(frame);
397 		if (ret == 0) {
398 			if (type == SMB_ACL_TYPE_ACCESS) {
399 				return mode_to_smb_acl(mode, mem_ctx);
400 			}
401 			if (S_ISDIR(mode)) {
402 				return sys_acl_init(mem_ctx);
403 			}
404 			errno = EACCES;
405 		}
406 	}
407 	return NULL;
408 }
409 
posixacl_xattr_acl_get_fd(vfs_handle_struct * handle,files_struct * fsp,TALLOC_CTX * mem_ctx)410 SMB_ACL_T posixacl_xattr_acl_get_fd(vfs_handle_struct *handle,
411 				    files_struct *fsp,
412 				    TALLOC_CTX *mem_ctx)
413 {
414 	int ret;
415 	int size = ACL_EA_SIZE(20);
416 	char *buf = alloca(size);
417 
418 	if (!buf) {
419 		return NULL;
420 	}
421 
422 	ret = SMB_VFS_FGETXATTR(fsp, ACL_EA_ACCESS, buf, size);
423 	if (ret < 0 && errno == ERANGE) {
424 		size = SMB_VFS_FGETXATTR(fsp, ACL_EA_ACCESS, NULL, 0);
425 		if (size > 0) {
426 			buf = alloca(size);
427 			if (!buf) {
428 				return NULL;
429 			}
430 			ret = SMB_VFS_FGETXATTR(fsp, ACL_EA_ACCESS, buf, size);
431 		}
432 	}
433 
434 	if (ret > 0) {
435 		return posixacl_xattr_to_smb_acl(buf, ret, mem_ctx);
436 	}
437 	if (ret == 0 || errno == ENOATTR) {
438 		SMB_STRUCT_STAT sbuf;
439 		ret = SMB_VFS_FSTAT(fsp, &sbuf);
440 		if (ret == 0)
441 			return mode_to_smb_acl(sbuf.st_ex_mode, mem_ctx);
442 	}
443 	return NULL;
444 }
445 
posixacl_xattr_acl_set_file(vfs_handle_struct * handle,const struct smb_filename * smb_fname,SMB_ACL_TYPE_T type,SMB_ACL_T theacl)446 int posixacl_xattr_acl_set_file(vfs_handle_struct *handle,
447 				const struct smb_filename *smb_fname,
448 				SMB_ACL_TYPE_T type,
449 				SMB_ACL_T theacl)
450 {
451 	const char *name;
452 	char *buf;
453 	ssize_t size;
454 	int ret;
455 
456 	size = smb_acl_to_posixacl_xattr(theacl, NULL, 0);
457 	buf = alloca(size);
458 	if (!buf) {
459 		return -1;
460 	}
461 
462 	ret = smb_acl_to_posixacl_xattr(theacl, buf, size);
463 	if (ret < 0) {
464 		errno = -ret;
465 		return -1;
466 	}
467 
468 	if (type == SMB_ACL_TYPE_ACCESS) {
469 		name = ACL_EA_ACCESS;
470 	} else if (type == SMB_ACL_TYPE_DEFAULT) {
471 		name = ACL_EA_DEFAULT;
472 	} else {
473 		errno = EINVAL;
474 		return -1;
475 	}
476 
477 	return SMB_VFS_SETXATTR(handle->conn, smb_fname,
478 			name, buf, size, 0);
479 }
480 
posixacl_xattr_acl_set_fd(vfs_handle_struct * handle,files_struct * fsp,SMB_ACL_T theacl)481 int posixacl_xattr_acl_set_fd(vfs_handle_struct *handle,
482 			      files_struct *fsp, SMB_ACL_T theacl)
483 {
484 	char *buf;
485 	ssize_t size;
486 	int ret;
487 
488 	size = smb_acl_to_posixacl_xattr(theacl, NULL, 0);
489 	buf = alloca(size);
490 	if (!buf) {
491 		return -1;
492 	}
493 
494 	ret = smb_acl_to_posixacl_xattr(theacl, buf, size);
495 	if (ret < 0) {
496 		errno = -ret;
497 		return -1;
498 	}
499 
500 	return SMB_VFS_FSETXATTR(fsp, ACL_EA_ACCESS, buf, size, 0);
501 }
502 
posixacl_xattr_acl_delete_def_file(vfs_handle_struct * handle,const struct smb_filename * smb_fname)503 int posixacl_xattr_acl_delete_def_file(vfs_handle_struct *handle,
504 				const struct smb_filename *smb_fname)
505 {
506 	return SMB_VFS_REMOVEXATTR(handle->conn,
507 			smb_fname,
508 			ACL_EA_DEFAULT);
509 }
510