1 /*
2 Copyright (c) 2010 Frank Lahm <franklahm@gmail.com>
3 Copyright (c) 2011 Laura Mueller <laura-mueller@uni-duesseldorf.de>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14 */
15
16 #ifdef HAVE_CONFIG_H
17 #include "config.h"
18 #endif /* HAVE_CONFIG_H */
19
20 #ifdef HAVE_ACLS
21
22 #include <unistd.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <time.h>
29 #include <errno.h>
30 #include <sys/acl.h>
31
32 #include <atalk/logger.h>
33 #include <atalk/afp.h>
34 #include <atalk/util.h>
35 #include <atalk/acl.h>
36 #include <atalk/unix.h>
37
38 #ifdef HAVE_NFSV4_ACLS
39
40 /* Get ACL. Allocates storage as needed. Caller must free.
41 * Returns no of ACEs or -1 on error. */
get_nfsv4_acl(const char * name,ace_t ** retAces)42 int get_nfsv4_acl(const char *name, ace_t **retAces)
43 {
44 int ace_count = -1;
45 ace_t *aces;
46 struct stat st;
47
48 *retAces = NULL;
49 /* Only call acl() for regular files and directories, otherwise just return 0 */
50 if (lstat(name, &st) != 0) {
51 LOG(log_debug, logtype_afpd, "get_nfsv4_acl(\"%s/%s\"): %s", getcwdpath(), name, strerror(errno));
52 return -1;
53 }
54
55 if (S_ISLNK(st.st_mode))
56 /* sorry, no ACLs for symlinks */
57 return 0;
58
59 if ( ! (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode))) {
60 LOG(log_debug, logtype_afpd, "get_nfsv4_acl(\"%s/%s\"): special", getcwdpath(), name);
61 return 0;
62 }
63
64 if ((ace_count = acl(name, ACE_GETACLCNT, 0, NULL)) == 0) {
65 LOG(log_debug, logtype_afpd, "get_nfsv4_acl(\"%s/%s\"): 0 ACEs", getcwdpath(), name);
66 return 0;
67 }
68
69 if (ace_count == -1) {
70 LOG(log_debug, logtype_afpd, "get_nfsv4_acl: acl('%s/%s', ACE_GETACLCNT): ace_count %i, error: %s",
71 getcwdpath(), name, ace_count, strerror(errno));
72 return -1;
73 }
74
75 aces = malloc(ace_count * sizeof(ace_t));
76 if (aces == NULL) {
77 LOG(log_error, logtype_afpd, "get_nfsv4_acl: malloc error");
78 return -1;
79 }
80
81 if ( (acl(name, ACE_GETACL, ace_count, aces)) == -1 ) {
82 LOG(log_error, logtype_afpd, "get_nfsv4_acl: acl(ACE_GETACL) error");
83 free(aces);
84 return -1;
85 }
86
87 LOG(log_debug9, logtype_afpd, "get_nfsv4_acl: file: %s -> No. of ACEs: %d", name, ace_count);
88 *retAces = aces;
89
90 return ace_count;
91 }
92
93 /*
94 Concatenate ACEs
95 */
concat_aces(ace_t * aces1,int ace1count,ace_t * aces2,int ace2count)96 ace_t *concat_aces(ace_t *aces1, int ace1count, ace_t *aces2, int ace2count)
97 {
98 ace_t *new_aces;
99 int i, j;
100
101 /* malloc buffer for new ACL */
102 if ((new_aces = malloc((ace1count + ace2count) * sizeof(ace_t))) == NULL) {
103 LOG(log_error, logtype_afpd, "combine_aces: malloc %s", strerror(errno));
104 return NULL;
105 }
106
107 /* Copy ACEs from buf1 */
108 for (i=0; i < ace1count; ) {
109 memcpy(&new_aces[i], &aces1[i], sizeof(ace_t));
110 i++;
111 }
112
113 j = i;
114
115 /* Copy ACEs from buf2 */
116 for (i=0; i < ace2count; ) {
117 memcpy(&new_aces[j], &aces2[i], sizeof(ace_t));
118 i++;
119 j++;
120 }
121 return new_aces;
122 }
123
124 /*
125 Remove any trivial ACE "in-place". Returns no of non-trivial ACEs
126 */
strip_trivial_aces(ace_t ** saces,int sacecount)127 int strip_trivial_aces(ace_t **saces, int sacecount)
128 {
129 int i,j;
130 int nontrivaces = 0;
131 ace_t *aces = *saces;
132 ace_t *new_aces;
133
134 if (aces == NULL || sacecount <= 0)
135 return 0;
136
137 /* Count non-trivial ACEs */
138 for (i=0; i < sacecount; ) {
139 if ( ! (aces[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_EVERYONE)))
140 nontrivaces++;
141 i++;
142 }
143 /* malloc buffer for new ACL */
144 if ((new_aces = malloc(nontrivaces * sizeof(ace_t))) == NULL) {
145 LOG(log_error, logtype_afpd, "strip_trivial_aces: malloc %s", strerror(errno));
146 return -1;
147 }
148
149 /* Copy non-trivial ACEs */
150 for (i=0, j=0; i < sacecount; ) {
151 if ( ! (aces[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_EVERYONE))) {
152 memcpy(&new_aces[j], &aces[i], sizeof(ace_t));
153 j++;
154 }
155 i++;
156 }
157
158 free(aces);
159 *saces = new_aces;
160
161 LOG(log_debug7, logtype_afpd, "strip_trivial_aces: non-trivial ACEs: %d", nontrivaces);
162
163 return nontrivaces;
164 }
165
166 /*
167 Remove non-trivial ACEs "in-place". Returns no of trivial ACEs.
168 */
strip_nontrivial_aces(ace_t ** saces,int sacecount)169 int strip_nontrivial_aces(ace_t **saces, int sacecount)
170 {
171 int i,j;
172 int trivaces = 0;
173 ace_t *aces = *saces;
174 ace_t *new_aces;
175
176 /* Count trivial ACEs */
177 for (i=0; i < sacecount; ) {
178 if ((aces[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_EVERYONE)))
179 trivaces++;
180 i++;
181 }
182 /* malloc buffer for new ACL */
183 if ((new_aces = malloc(trivaces * sizeof(ace_t))) == NULL) {
184 LOG(log_error, logtype_afpd, "strip_nontrivial_aces: malloc %s", strerror(errno));
185 return -1;
186 }
187
188 /* Copy trivial ACEs */
189 for (i=0, j=0; i < sacecount; ) {
190 if ((aces[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_EVERYONE))) {
191 memcpy(&new_aces[j], &aces[i], sizeof(ace_t));
192 j++;
193 }
194 i++;
195 }
196 /* Free old ACEs */
197 free(aces);
198 *saces = new_aces;
199
200 LOG(log_debug7, logtype_afpd, "strip_nontrivial_aces: trivial ACEs: %d", trivaces);
201
202 return trivaces;
203 }
204
205 /*!
206 * Change mode of file preserving existing explicit ACEs
207 *
208 * nfsv4_chmod
209 * (1) reads objects ACL (acl1), may return 0 or -1 NFSv4 ACEs on eg UFS fs
210 * (2) removes all trivial ACEs from the ACL by calling strip_trivial_aces(), possibly
211 * leaving 0 ACEs in the ACL if there were only trivial ACEs as mapped from the mode
212 * (3) calls chmod() with mode, we're done if step (1) returned 0 for noaces
213 * (4) reads the changed ACL (acl2) which
214 * a) might still contain explicit ACEs (up to onnv132)
215 * b) will have any explicit ACE removed (starting with onnv145/Openindiana)
216 * (5) strip any explicit ACE from acl2 using strip_nontrivial_aces()
217 * (6) merge acl2 and acl2
218 * (7) set the ACL merged ACL on the object
219 */
nfsv4_chmod(char * name,mode_t mode)220 int nfsv4_chmod(char *name, mode_t mode)
221 {
222 int ret = -1;
223 int noaces, nnaces;
224 ace_t *oacl = NULL, *nacl = NULL, *cacl = NULL;
225
226 LOG(log_debug, logtype_afpd, "nfsv4_chmod(\"%s/%s\", %04o)",
227 getcwdpath(), name, mode);
228
229 if ((noaces = get_nfsv4_acl(name, &oacl)) < 1) /* (1) */
230 return chmod(name, mode);
231
232 if ((noaces = strip_trivial_aces(&oacl, noaces)) == -1) /* (2) */
233 goto exit;
234
235 if (chmod(name, mode) != 0) /* (3) */
236 goto exit;
237
238 if ((nnaces = get_nfsv4_acl(name, &nacl)) == -1) {/* (4) */
239 if (errno != EACCES)
240 goto exit;
241 become_root();
242 nnaces = get_nfsv4_acl(name, &nacl);
243 unbecome_root();
244 if (nnaces == -1)
245 goto exit;
246 }
247
248 if ((nnaces = strip_nontrivial_aces(&nacl, nnaces)) == -1) /* (5) */
249 goto exit;
250
251 if ((cacl = concat_aces(oacl, noaces, nacl, nnaces)) == NULL) /* (6) */
252 goto exit;
253
254 if ((ret = acl(name, ACE_SETACL, noaces + nnaces, cacl)) != 0) {
255 if (errno != EACCES) {
256 LOG(log_error, logtype_afpd, "nfsv4_chmod: error setting acl: %s", strerror(errno));
257 goto exit;
258 }
259 become_root();
260 ret = acl(name, ACE_SETACL, noaces + nnaces, cacl);
261 unbecome_root();
262 if (ret != 0) {
263 LOG(log_error, logtype_afpd, "nfsv4_chmod: error setting acl: %s", strerror(errno));
264 goto exit;
265 }
266 }
267
268 exit:
269 if (oacl) free(oacl);
270 if (nacl) free(nacl);
271 if (cacl) free(cacl);
272
273 LOG(log_debug, logtype_afpd, "nfsv4_chmod(\"%s/%s\", %04o): result: %d",
274 getcwdpath(), name, mode, ret);
275
276 return ret;
277 }
278
279 #endif /* HAVE_NFSV4_ACLS */
280
281 #ifdef HAVE_POSIX_ACLS
282
283 /* This is a workaround for chmod() on filestystems supporting Posix 1003.1e draft 17
284 * compliant ACLs. For objects with extented ACLs, eg objects with an ACL_MASK entry,
285 * chmod() manipulates ACL_MASK instead of ACL_GROUP_OBJ. As OS X isn't aware of
286 * this behavior calling FPSetFileDirParms may lead to unpredictable results. For
287 * more information see section 23.1.2 of Posix 1003.1e draft 17.
288 *
289 * posix_chmod() accepts the same arguments as chmod() and returns 0 in case of
290 * success or -1 in case something went wrong.
291 */
292
293 #define SEARCH_GROUP_OBJ 0x01
294 #define SEARCH_MASK 0x02
295
posix_chmod(const char * name,mode_t mode)296 int posix_chmod(const char *name, mode_t mode) {
297 int ret = 0;
298 int entry_id = ACL_FIRST_ENTRY;
299 acl_entry_t entry;
300 acl_entry_t group_entry;
301 acl_tag_t tag;
302 acl_t acl;
303 u_char not_found = (SEARCH_GROUP_OBJ|SEARCH_MASK); /* used as flags */
304
305 LOG(log_maxdebug, logtype_afpd, "posix_chmod(\"%s\", mode: %04o) BEGIN",
306 fullpathname(name), mode);
307
308 /* Call chmod() first because there might be some special bits to be set which
309 * aren't related to access control.
310 */
311 #ifdef BSD4_4
312 /*
313 * On FreeBSD chmod_acl() ends up in here too, but on
314 * FreeBSD sine ~9.1 with ZFS doesn't allow setting the g+s bit.
315 * Fixes PR #491.
316 */
317 mode &= 0777;
318 #endif
319 ret = chmod(name, mode);
320
321 if (ret)
322 goto done;
323
324 /* Check if the underlying filesystem supports ACLs. */
325 acl = acl_get_file(name, ACL_TYPE_ACCESS);
326
327 if (acl) {
328 /* There is no need to keep iterating once we have found ACL_GROUP_OBJ and ACL_MASK. */
329 while ((acl_get_entry(acl, entry_id, &entry) == 1) && not_found) {
330 entry_id = ACL_NEXT_ENTRY;
331
332 ret = acl_get_tag_type(entry, &tag);
333
334 if (ret) {
335 LOG(log_error, logtype_afpd, "posix_chmod: Failed to get tag type.");
336 goto cleanup;
337 }
338
339 switch (tag) {
340 case ACL_GROUP_OBJ:
341 group_entry = entry;
342 not_found &= ~SEARCH_GROUP_OBJ;
343 break;
344
345 case ACL_MASK:
346 not_found &= ~SEARCH_MASK;
347 break;
348
349 default:
350 break;
351 }
352 }
353 if (!not_found) {
354 /* The filesystem object has extented ACLs. We have to update ACL_GROUP_OBJ
355 * with the group permissions.
356 */
357 acl_permset_t permset;
358 acl_perm_t perm = 0;
359
360 ret = acl_get_permset(group_entry, &permset);
361
362 if (ret) {
363 LOG(log_error, logtype_afpd, "posix_chmod: Can't get permset.");
364 goto cleanup;
365 }
366 ret = acl_clear_perms(permset);
367
368 if (ret)
369 goto cleanup;
370
371 if (mode & S_IXGRP)
372 perm |= ACL_EXECUTE;
373
374 if (mode & S_IWGRP)
375 perm |= ACL_WRITE;
376
377 if (mode & S_IRGRP)
378 perm |= ACL_READ;
379
380 ret = acl_add_perm(permset, perm);
381
382 if (ret)
383 goto cleanup;
384
385 ret = acl_set_permset(group_entry, permset);
386
387 if (ret) {
388 LOG(log_error, logtype_afpd, "posix_chmod: Can't set permset.");
389 goto cleanup;
390 }
391 /* also update ACL_MASK */
392 ret = acl_calc_mask(&acl);
393
394 if (ret) {
395 LOG(log_error, logtype_afpd, "posix_chmod: acl_calc_mask failed.");
396 goto cleanup;
397 }
398 ret = acl_set_file(name, ACL_TYPE_ACCESS, acl);
399 }
400 cleanup:
401 acl_free(acl);
402 }
403 done:
404 LOG(log_maxdebug, logtype_afpd, "posix_chmod(\"%s\", mode: %04o): END: %d",
405 fullpathname(name), mode, ret);
406 return ret;
407 }
408
409 /*
410 * posix_fchmod() accepts the same arguments as fchmod() and returns 0 in case of
411 * success or -1 in case something went wrong.
412 */
posix_fchmod(int fd,mode_t mode)413 int posix_fchmod(int fd, mode_t mode) {
414 int ret = 0;
415 int entry_id = ACL_FIRST_ENTRY;
416 acl_entry_t entry;
417 acl_entry_t group_entry;
418 acl_tag_t tag;
419 acl_t acl;
420 u_char not_found = (SEARCH_GROUP_OBJ|SEARCH_MASK); /* used as flags */
421
422 /* Call chmod() first because there might be some special bits to be set which
423 * aren't related to access control.
424 */
425 ret = fchmod(fd, mode);
426
427 if (ret)
428 goto done;
429
430 /* Check if the underlying filesystem supports ACLs. */
431 acl = acl_get_fd(fd);
432
433 if (acl) {
434 /* There is no need to keep iterating once we have found ACL_GROUP_OBJ and ACL_MASK. */
435 while ((acl_get_entry(acl, entry_id, &entry) == 1) && not_found) {
436 entry_id = ACL_NEXT_ENTRY;
437
438 ret = acl_get_tag_type(entry, &tag);
439
440 if (ret) {
441 LOG(log_error, logtype_afpd, "posix_fchmod: Failed to get tag type.");
442 goto cleanup;
443 }
444
445 switch (tag) {
446 case ACL_GROUP_OBJ:
447 group_entry = entry;
448 not_found &= ~SEARCH_GROUP_OBJ;
449 break;
450
451 case ACL_MASK:
452 not_found &= ~SEARCH_MASK;
453 break;
454
455 default:
456 break;
457 }
458 }
459 if (!not_found) {
460 /* The filesystem object has extented ACLs. We have to update ACL_GROUP_OBJ
461 * with the group permissions.
462 */
463 acl_permset_t permset;
464 acl_perm_t perm = 0;
465
466 ret = acl_get_permset(group_entry, &permset);
467
468 if (ret) {
469 LOG(log_error, logtype_afpd, "posix_fchmod: Can't get permset.");
470 goto cleanup;
471 }
472 ret = acl_clear_perms(permset);
473
474 if (ret)
475 goto cleanup;
476
477 if (mode & S_IXGRP)
478 perm |= ACL_EXECUTE;
479
480 if (mode & S_IWGRP)
481 perm |= ACL_WRITE;
482
483 if (mode & S_IRGRP)
484 perm |= ACL_READ;
485
486 ret = acl_add_perm(permset, perm);
487
488 if (ret)
489 goto cleanup;
490
491 ret = acl_set_permset(group_entry, permset);
492
493 if (ret) {
494 LOG(log_error, logtype_afpd, "posix_fchmod: Can't set permset.");
495 goto cleanup;
496 }
497 /* also update ACL_MASK */
498 ret = acl_calc_mask(&acl);
499
500 if (ret) {
501 LOG(log_error, logtype_afpd, "posix_fchmod: acl_calc_mask failed.");
502 goto cleanup;
503 }
504 ret = acl_set_fd(fd, acl);
505 }
506 cleanup:
507 acl_free(acl);
508 }
509 done:
510 return ret;
511 }
512
513 #endif /* HAVE_POSIX_ACLS */
514
515 #endif /* HAVE_ACLS */
516