libsmb: Fix an error path memleak
[Samba.git] / source3 / modules / posixacl_xattr.c
blob8f6f365bff969e0bfda384d7451042dc1c6260ea
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>
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.
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.
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/>.
21 #include "includes.h"
22 #include "system/filesys.h"
23 #include "smbd/smbd.h"
24 #include "modules/posixacl_xattr.h"
27 POSIX ACL Format:
29 Size = 4 (header) + N * 8 (entry)
31 Offset Size Field (Little Endian)
32 -------------------------------------
33 0-3 4-byte Version
35 4-5 2-byte Entry-1 tag
36 6-7 2-byte Entry-1 perm
37 8-11 4-byte Entry-1 id
39 12-13 2-byte Entry-2 tag
40 14-15 2-byte Entry-2 perm
41 16-19 4-byte Entry-2 id
43 ...
49 /* private functions */
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
57 #define ACL_EA_SIZE(n) (ACL_EA_HEADER_SIZE + ((n) * ACL_EA_ENTRY_SIZE))
59 static SMB_ACL_T mode_to_smb_acl(mode_t mode, TALLOC_CTX *mem_ctx)
61 struct smb_acl_t *result;
62 int count;
64 count = 3;
65 result = sys_acl_init(mem_ctx);
66 if (!result) {
67 return NULL;
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;
77 result->count = count;
79 result->acl[0].a_type = SMB_ACL_USER_OBJ;
80 result->acl[0].a_perm = (mode & S_IRWXU) >> 6;
82 result->acl[1].a_type = SMB_ACL_GROUP_OBJ;
83 result->acl[1].a_perm = (mode & S_IRWXG) >> 3;
85 result->acl[2].a_type = SMB_ACL_OTHER;
86 result->acl[2].a_perm = mode & S_IRWXO;
88 return result;
91 static SMB_ACL_T posixacl_xattr_to_smb_acl(const char *buf, size_t xattr_size,
92 TALLOC_CTX *mem_ctx)
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;
104 size = xattr_size;
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;
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;
119 offset = ACL_EA_HEADER_SIZE;
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)
126 DEBUG(0, ("Invalid ACL EA size: %d\n", size));
127 errno = EINVAL;
128 return NULL;
131 count = size / ACL_EA_ENTRY_SIZE;
133 result = sys_acl_init(mem_ctx);
134 if (!result) {
135 return NULL;
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;
145 result->count = count;
147 smb_ace = result->acl;
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;
154 /* PERM is the next 2 bytes of an entry */
155 perm = SVAL(buf, offset);
156 offset += 2;
158 /* ID is the last 4 bytes of an entry */
159 id = IVAL(buf, offset);
160 offset += 4;
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;
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;
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);
204 smb_ace++;
207 return result;
211 static int posixacl_xattr_entry_compare(const void *left, const void *right)
213 int ret = 0;
214 uint16_t tag_left, tag_right;
215 uint32_t id_left, id_right;
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
225 /* TAG is the first element in the entry */
226 tag_left = SVAL(left, 0);
227 tag_right = SVAL(right, 0);
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.
234 id_left = IVAL(left, 4);
235 id_right = IVAL(right, 4);
236 ret = id_left - id_right;
239 return ret;
243 static int smb_acl_to_posixacl_xattr(SMB_ACL_T theacl, char *buf, size_t len)
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;
254 count = theacl->count;
256 size = ACL_EA_SIZE(count);
257 if (!buf) {
258 return size;
260 if (len < size) {
261 return -ERANGE;
263 smb_ace = theacl->acl;
265 /* Version is the first 4 bytes of the ACL */
266 SIVAL(buf, 0, ACL_EA_VERSION);
267 offset = ACL_EA_HEADER_SIZE;
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;
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;
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;
316 /* TAG is the first 2 bytes of an entry */
317 SSVAL(buf, offset, tag);
318 offset += 2;
320 /* PERM is the next 2 bytes of an entry */
321 SSVAL(buf, offset, perm);
322 offset += 2;
324 /* ID is the last 4 bytes of an entry */
325 SIVAL(buf, offset, id);
326 offset += 4;
328 smb_ace++;
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);
335 return size;
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)
343 int ret;
344 int size;
345 char *buf;
346 const char *name;
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;
357 size = ACL_EA_SIZE(20);
358 buf = alloca(size);
359 if (!buf) {
360 return NULL;
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;
373 ret = SMB_VFS_GETXATTR(handle->conn,
374 smb_fname, name,
375 buf, size);
379 if (ret > 0) {
380 return posixacl_xattr_to_smb_acl(buf, ret, mem_ctx);
382 if (ret == 0 || errno == ENOATTR || errno == ENODATA) {
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;
396 TALLOC_FREE(frame);
397 if (ret == 0) {
398 if (type == SMB_ACL_TYPE_ACCESS) {
399 return mode_to_smb_acl(mode, mem_ctx);
401 if (S_ISDIR(mode)) {
402 return sys_acl_init(mem_ctx);
404 errno = EACCES;
407 return NULL;
410 SMB_ACL_T posixacl_xattr_acl_get_fd(vfs_handle_struct *handle,
411 files_struct *fsp,
412 TALLOC_CTX *mem_ctx)
414 int ret;
415 int size = ACL_EA_SIZE(20);
416 char *buf = alloca(size);
418 if (!buf) {
419 return NULL;
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;
430 ret = SMB_VFS_FGETXATTR(fsp, ACL_EA_ACCESS, buf, size);
434 if (ret > 0) {
435 return posixacl_xattr_to_smb_acl(buf, ret, mem_ctx);
437 if (ret == 0 || errno == ENOATTR || errno == ENODATA) {
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);
443 return NULL;
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)
451 const char *name;
452 char *buf;
453 ssize_t size;
454 int ret;
456 size = smb_acl_to_posixacl_xattr(theacl, NULL, 0);
457 buf = alloca(size);
458 if (!buf) {
459 return -1;
462 ret = smb_acl_to_posixacl_xattr(theacl, buf, size);
463 if (ret < 0) {
464 errno = -ret;
465 return -1;
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;
477 return SMB_VFS_SETXATTR(handle->conn, smb_fname,
478 name, buf, size, 0);
481 int posixacl_xattr_acl_set_fd(vfs_handle_struct *handle,
482 files_struct *fsp, SMB_ACL_T theacl)
484 char *buf;
485 ssize_t size;
486 int ret;
488 size = smb_acl_to_posixacl_xattr(theacl, NULL, 0);
489 buf = alloca(size);
490 if (!buf) {
491 return -1;
494 ret = smb_acl_to_posixacl_xattr(theacl, buf, size);
495 if (ret < 0) {
496 errno = -ret;
497 return -1;
500 return SMB_VFS_FSETXATTR(fsp, ACL_EA_ACCESS, buf, size, 0);
503 int posixacl_xattr_acl_delete_def_file(vfs_handle_struct *handle,
504 const struct smb_filename *smb_fname)
506 return SMB_VFS_REMOVEXATTR(handle->conn,
507 smb_fname,
508 ACL_EA_DEFAULT);