• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /netgear-WNDR4500v2-V1.0.0.60_1.0.38/ap/gpl/timemachine/netatalk-2.2.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: 0x%08x", name, mode);
306
307    /* Call chmod() first because there might be some special bits to be set which
308     * aren't related to access control.
309     */
310    ret = chmod(name, mode);
311
312    if (ret)
313	goto done;
314
315    /* Check if the underlying filesystem supports ACLs. */
316    acl = acl_get_file(name, ACL_TYPE_ACCESS);
317
318    if (acl) {
319        /* There is no need to keep iterating once we have found ACL_GROUP_OBJ and ACL_MASK. */
320        while ((acl_get_entry(acl, entry_id, &entry) == 1) && not_found) {
321            entry_id = ACL_NEXT_ENTRY;
322
323            ret = acl_get_tag_type(entry, &tag);
324
325	    if (ret) {
326                LOG(log_error, logtype_afpd, "posix_chmod: Failed to get tag type.");
327                goto cleanup;
328	    }
329
330            switch (tag) {
331                case ACL_GROUP_OBJ:
332                    group_entry = entry;
333                    not_found &= ~SEARCH_GROUP_OBJ;
334                    break;
335
336                case ACL_MASK:
337                    not_found &= ~SEARCH_MASK;
338                    break;
339
340                default:
341                    break;
342            }
343        }
344        if (!not_found) {
345            /* The filesystem object has extented ACLs. We have to update ACL_GROUP_OBJ
346             * with the group permissions.
347             */
348	    acl_permset_t permset;
349            acl_perm_t perm = 0;
350
351            ret = acl_get_permset(group_entry, &permset);
352
353            if (ret) {
354                LOG(log_error, logtype_afpd, "posix_chmod: Can't get permset.");
355                goto cleanup;
356            }
357            ret = acl_clear_perms(permset);
358
359            if (ret)
360                goto cleanup;
361
362            if (mode & S_IXGRP)
363                perm |= ACL_EXECUTE;
364
365            if (mode & S_IWGRP)
366                perm |= ACL_WRITE;
367
368            if (mode & S_IRGRP)
369                perm |= ACL_READ;
370
371            ret = acl_add_perm(permset, perm);
372
373            if (ret)
374                goto cleanup;
375
376            ret = acl_set_permset(group_entry, permset);
377
378            if (ret) {
379                LOG(log_error, logtype_afpd, "posix_chmod: Can't set permset.");
380                goto cleanup;
381            }
382            /* also update ACL_MASK */
383            ret = acl_calc_mask(&acl);
384
385	    if (ret) {
386                LOG(log_error, logtype_afpd, "posix_chmod: acl_calc_mask failed.");
387	        goto cleanup;
388            }
389	    ret = acl_set_file(name, ACL_TYPE_ACCESS, acl);
390        }
391cleanup:
392        acl_free(acl);
393    }
394done:
395    LOG(log_maxdebug, logtype_afpd, "posix_chmod: %d", ret);
396    return ret;
397}
398
399/*
400 * posix_fchmod() accepts the same arguments as fchmod() and returns 0 in case of
401 * success or -1 in case something went wrong.
402 */
403int posix_fchmod(int fd, mode_t mode) {
404    int ret = 0;
405    int entry_id = ACL_FIRST_ENTRY;
406    acl_entry_t entry;
407    acl_entry_t group_entry;
408    acl_tag_t tag;
409    acl_t acl;
410    u_char not_found = (SEARCH_GROUP_OBJ|SEARCH_MASK); /* used as flags */
411
412    /* Call chmod() first because there might be some special bits to be set which
413     * aren't related to access control.
414     */
415    ret = fchmod(fd, mode);
416
417    if (ret)
418        goto done;
419
420    /* Check if the underlying filesystem supports ACLs. */
421    acl = acl_get_fd(fd);
422
423    if (acl) {
424        /* There is no need to keep iterating once we have found ACL_GROUP_OBJ and ACL_MASK. */
425        while ((acl_get_entry(acl, entry_id, &entry) == 1) && not_found) {
426            entry_id = ACL_NEXT_ENTRY;
427
428            ret = acl_get_tag_type(entry, &tag);
429
430            if (ret) {
431                LOG(log_error, logtype_afpd, "posix_fchmod: Failed to get tag type.");
432                goto cleanup;
433            }
434
435            switch (tag) {
436                case ACL_GROUP_OBJ:
437                    group_entry = entry;
438                    not_found &= ~SEARCH_GROUP_OBJ;
439                    break;
440
441                case ACL_MASK:
442                    not_found &= ~SEARCH_MASK;
443                    break;
444
445                default:
446                    break;
447            }
448        }
449        if (!not_found) {
450            /* The filesystem object has extented ACLs. We have to update ACL_GROUP_OBJ
451             * with the group permissions.
452             */
453            acl_permset_t permset;
454            acl_perm_t perm = 0;
455
456            ret = acl_get_permset(group_entry, &permset);
457
458            if (ret) {
459                LOG(log_error, logtype_afpd, "posix_fchmod: Can't get permset.");
460                goto cleanup;
461            }
462            ret = acl_clear_perms(permset);
463
464            if (ret)
465                goto cleanup;
466
467            if (mode & S_IXGRP)
468                perm |= ACL_EXECUTE;
469
470            if (mode & S_IWGRP)
471                perm |= ACL_WRITE;
472
473            if (mode & S_IRGRP)
474                perm |= ACL_READ;
475
476            ret = acl_add_perm(permset, perm);
477
478            if (ret)
479                goto cleanup;
480
481            ret = acl_set_permset(group_entry, permset);
482
483            if (ret) {
484                LOG(log_error, logtype_afpd, "posix_fchmod: Can't set permset.");
485                goto cleanup;
486            }
487            /* also update ACL_MASK */
488            ret = acl_calc_mask(&acl);
489
490            if (ret) {
491                LOG(log_error, logtype_afpd, "posix_fchmod: acl_calc_mask failed.");
492                goto cleanup;
493            }
494            ret = acl_set_fd(fd, acl);
495        }
496cleanup:
497        acl_free(acl);
498    }
499done:
500    return ret;
501}
502
503#endif /* HAVE_POSIX_ACLS */
504
505#endif /* HAVE_ACLS */
506