• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /asuswrt-rt-n18u-9.0.0.4.380.2695/release/src-rt/router/netatalk-3.0.5/libatalk/acl/
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_SOLARIS_ACLS
39
40/* Get ACL. Allocates storage as needed. Caller must free.
41 * Returns no of ACEs or -1 on error.  */
42int 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*/
96ace_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*/
127int 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*/
169int 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 */
220int 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
268exit:
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_SOLARIS_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
296int 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        }
400cleanup:
401        acl_free(acl);
402    }
403done:
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 */
413int 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        }
506cleanup:
507        acl_free(acl);
508    }
509done:
510    return ret;
511}
512
513#endif /* HAVE_POSIX_ACLS */
514
515#endif /* HAVE_ACLS */
516