s3: VFS: Change SMB_VFS_SYS_ACL_GET_FILE to use const struct smb_filename * instead...
[samba.git] / source3 / modules / posixacl_xattr.c
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
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
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
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
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
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->base_name,
364                                 name, buf, size);
365         if (ret < 0 && errno == ERANGE) {
366                 size = SMB_VFS_GETXATTR(handle->conn, smb_fname->base_name,
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->base_name, 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 || 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 == 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
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 || 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);
442         }
443         return NULL;
444 }
445
446 int posixacl_xattr_acl_set_file(vfs_handle_struct *handle,
447                                 const char *path_p,
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, path_p, name, buf, size, 0);
478 }
479
480 int posixacl_xattr_acl_set_fd(vfs_handle_struct *handle,
481                               files_struct *fsp, SMB_ACL_T theacl)
482 {
483         char *buf;
484         ssize_t size;
485         int ret;
486
487         size = smb_acl_to_posixacl_xattr(theacl, NULL, 0);
488         buf = alloca(size);
489         if (!buf) {
490                 return -1;
491         }
492
493         ret = smb_acl_to_posixacl_xattr(theacl, buf, size);
494         if (ret < 0) {
495                 errno = -ret;
496                 return -1;
497         }
498
499         return SMB_VFS_FSETXATTR(fsp, ACL_EA_ACCESS, buf, size, 0);
500 }
501
502 int posixacl_xattr_acl_delete_def_file(vfs_handle_struct *handle,
503                                 const struct smb_filename *smb_fname)
504 {
505         return SMB_VFS_REMOVEXATTR(handle->conn,
506                         smb_fname->base_name,
507                         ACL_EA_DEFAULT);
508 }