1/* set-mode-acl.c - set access control list equivalent to a mode
2
3   Copyright (C) 2002-2003, 2005-2010 Free Software Foundation, Inc.
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 3 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   You should have received a copy of the GNU General Public License
16   along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18   Written by Paul Eggert and Andreas Gruenbacher, and Bruno Haible.  */
19
20#include <config.h>
21
22#include "acl.h"
23
24#include "acl-internal.h"
25
26#include "gettext.h"
27#define _(msgid) gettext (msgid)
28
29
30/* If DESC is a valid file descriptor use fchmod to change the
31   file's mode to MODE on systems that have fchown. On systems
32   that don't have fchown and if DESC is invalid, use chown on
33   NAME instead.
34   Return 0 if successful.  Return -1 and set errno upon failure.  */
35
36int
37chmod_or_fchmod (const char *name, int desc, mode_t mode)
38{
39  if (HAVE_FCHMOD && desc != -1)
40    return fchmod (desc, mode);
41  else
42    return chmod (name, mode);
43}
44
45/* Set the access control lists of a file. If DESC is a valid file
46   descriptor, use file descriptor operations where available, else use
47   filename based operations on NAME.  If access control lists are not
48   available, fchmod the target file to MODE.  Also sets the
49   non-permission bits of the destination file (S_ISUID, S_ISGID, S_ISVTX)
50   to those from MODE if any are set.
51   Return 0 if successful.  Return -1 and set errno upon failure.  */
52
53int
54qset_acl (char const *name, int desc, mode_t mode)
55{
56#if USE_ACL
57# if HAVE_ACL_GET_FILE
58  /* POSIX 1003.1e draft 17 (abandoned) specific version.  */
59  /* Linux, FreeBSD, MacOS X, IRIX, Tru64 */
60#  if MODE_INSIDE_ACL
61  /* Linux, FreeBSD, IRIX, Tru64 */
62
63  /* We must also have acl_from_text and acl_delete_def_file.
64     (acl_delete_def_file could be emulated with acl_init followed
65      by acl_set_file, but acl_set_file with an empty acl is
66      unspecified.)  */
67
68#   ifndef HAVE_ACL_FROM_TEXT
69#    error Must have acl_from_text (see POSIX 1003.1e draft 17).
70#   endif
71#   ifndef HAVE_ACL_DELETE_DEF_FILE
72#    error Must have acl_delete_def_file (see POSIX 1003.1e draft 17).
73#   endif
74
75  acl_t acl;
76  int ret;
77
78  if (HAVE_ACL_FROM_MODE) /* Linux */
79    {
80      acl = acl_from_mode (mode);
81      if (!acl)
82        return -1;
83    }
84  else /* FreeBSD, IRIX, Tru64 */
85    {
86      /* If we were to create the ACL using the functions acl_init(),
87         acl_create_entry(), acl_set_tag_type(), acl_set_qualifier(),
88         acl_get_permset(), acl_clear_perm[s](), acl_add_perm(), we
89         would need to create a qualifier.  I don't know how to do this.
90         So create it using acl_from_text().  */
91
92#   if HAVE_ACL_FREE_TEXT /* Tru64 */
93      char acl_text[] = "u::---,g::---,o::---,";
94#   else /* FreeBSD, IRIX */
95      char acl_text[] = "u::---,g::---,o::---";
96#   endif
97
98      if (mode & S_IRUSR) acl_text[ 3] = 'r';
99      if (mode & S_IWUSR) acl_text[ 4] = 'w';
100      if (mode & S_IXUSR) acl_text[ 5] = 'x';
101      if (mode & S_IRGRP) acl_text[10] = 'r';
102      if (mode & S_IWGRP) acl_text[11] = 'w';
103      if (mode & S_IXGRP) acl_text[12] = 'x';
104      if (mode & S_IROTH) acl_text[17] = 'r';
105      if (mode & S_IWOTH) acl_text[18] = 'w';
106      if (mode & S_IXOTH) acl_text[19] = 'x';
107
108      acl = acl_from_text (acl_text);
109      if (!acl)
110        return -1;
111    }
112  if (HAVE_ACL_SET_FD && desc != -1)
113    ret = acl_set_fd (desc, acl);
114  else
115    ret = acl_set_file (name, ACL_TYPE_ACCESS, acl);
116  if (ret != 0)
117    {
118      int saved_errno = errno;
119      acl_free (acl);
120
121      if (ACL_NOT_WELL_SUPPORTED (errno))
122        return chmod_or_fchmod (name, desc, mode);
123      else
124        {
125          errno = saved_errno;
126          return -1;
127        }
128    }
129  else
130    acl_free (acl);
131
132  if (S_ISDIR (mode) && acl_delete_def_file (name))
133    return -1;
134
135  if (mode & (S_ISUID | S_ISGID | S_ISVTX))
136    {
137      /* We did not call chmod so far, so the special bits have not yet
138         been set.  */
139      return chmod_or_fchmod (name, desc, mode);
140    }
141  return 0;
142
143#  else /* !MODE_INSIDE_ACL */
144  /* MacOS X */
145
146#   if !HAVE_ACL_TYPE_EXTENDED
147#    error Must have ACL_TYPE_EXTENDED
148#   endif
149
150  /* On MacOS X,  acl_get_file (name, ACL_TYPE_ACCESS)
151     and          acl_get_file (name, ACL_TYPE_DEFAULT)
152     always return NULL / EINVAL.  You have to use
153                  acl_get_file (name, ACL_TYPE_EXTENDED)
154     or           acl_get_fd (open (name, ...))
155     to retrieve an ACL.
156     On the other hand,
157                  acl_set_file (name, ACL_TYPE_ACCESS, acl)
158     and          acl_set_file (name, ACL_TYPE_DEFAULT, acl)
159     have the same effect as
160                  acl_set_file (name, ACL_TYPE_EXTENDED, acl):
161     Each of these calls sets the file's ACL.  */
162
163  acl_t acl;
164  int ret;
165
166  /* Remove the ACL if the file has ACLs.  */
167  if (HAVE_ACL_GET_FD && desc != -1)
168    acl = acl_get_fd (desc);
169  else
170    acl = acl_get_file (name, ACL_TYPE_EXTENDED);
171  if (acl)
172    {
173      acl_free (acl);
174
175      acl = acl_init (0);
176      if (acl)
177        {
178          if (HAVE_ACL_SET_FD && desc != -1)
179            ret = acl_set_fd (desc, acl);
180          else
181            ret = acl_set_file (name, ACL_TYPE_EXTENDED, acl);
182          if (ret != 0)
183            {
184              int saved_errno = errno;
185
186              acl_free (acl);
187
188              if (ACL_NOT_WELL_SUPPORTED (saved_errno))
189                return chmod_or_fchmod (name, desc, mode);
190              else
191                {
192                  errno = saved_errno;
193                  return -1;
194                }
195            }
196          acl_free (acl);
197        }
198    }
199
200  /* Since !MODE_INSIDE_ACL, we have to call chmod explicitly.  */
201  return chmod_or_fchmod (name, desc, mode);
202#  endif
203
204# elif HAVE_ACL && defined GETACLCNT /* Solaris, Cygwin, not HP-UX */
205
206#  if defined ACL_NO_TRIVIAL
207  /* Solaris 10 (newer version), which has additional API declared in
208     <sys/acl.h> (acl_t) and implemented in libsec (acl_set, acl_trivial,
209     acl_fromtext, ...).  */
210
211  acl_t *aclp;
212  char acl_text[] = "user::---,group::---,mask:---,other:---";
213  int ret;
214  int saved_errno;
215
216  if (mode & S_IRUSR) acl_text[ 6] = 'r';
217  if (mode & S_IWUSR) acl_text[ 7] = 'w';
218  if (mode & S_IXUSR) acl_text[ 8] = 'x';
219  if (mode & S_IRGRP) acl_text[17] = acl_text[26] = 'r';
220  if (mode & S_IWGRP) acl_text[18] = acl_text[27] = 'w';
221  if (mode & S_IXGRP) acl_text[19] = acl_text[28] = 'x';
222  if (mode & S_IROTH) acl_text[36] = 'r';
223  if (mode & S_IWOTH) acl_text[37] = 'w';
224  if (mode & S_IXOTH) acl_text[38] = 'x';
225
226  if (acl_fromtext (acl_text, &aclp) != 0)
227    {
228      errno = ENOMEM;
229      return -1;
230    }
231
232  ret = (desc < 0 ? acl_set (name, aclp) : facl_set (desc, aclp));
233  saved_errno = errno;
234  acl_free (aclp);
235  if (ret < 0)
236    {
237      if (saved_errno == ENOSYS || saved_errno == EOPNOTSUPP)
238        return chmod_or_fchmod (name, desc, mode);
239      errno = saved_errno;
240      return -1;
241    }
242
243  if (mode & (S_ISUID | S_ISGID | S_ISVTX))
244    {
245      /* We did not call chmod so far, so the special bits have not yet
246         been set.  */
247      return chmod_or_fchmod (name, desc, mode);
248    }
249  return 0;
250
251#  else /* Solaris, Cygwin, general case */
252
253#   ifdef ACE_GETACL
254  /* Solaris also has a different variant of ACLs, used in ZFS and NFSv4
255     file systems (whereas the other ones are used in UFS file systems).  */
256
257  /* The flags in the ace_t structure changed in a binary incompatible way
258     when ACL_NO_TRIVIAL etc. were introduced in <sys/acl.h> version 1.15.
259     How to distinguish the two conventions at runtime?
260     We fetch the existing ACL.  In the old convention, usually three ACEs have
261     a_flags = ACE_OWNER / ACE_GROUP / ACE_OTHER, in the range 0x0100..0x0400.
262     In the new convention, these values are not used.  */
263  int convention;
264
265  {
266    int count;
267    ace_t *entries;
268
269    for (;;)
270      {
271        if (desc != -1)
272          count = facl (desc, ACE_GETACLCNT, 0, NULL);
273        else
274          count = acl (name, ACE_GETACLCNT, 0, NULL);
275        if (count <= 0)
276          {
277            convention = -1;
278            break;
279          }
280        entries = (ace_t *) malloc (count * sizeof (ace_t));
281        if (entries == NULL)
282          {
283            errno = ENOMEM;
284            return -1;
285          }
286        if ((desc != -1
287             ? facl (desc, ACE_GETACL, count, entries)
288             : acl (name, ACE_GETACL, count, entries))
289            == count)
290          {
291            int i;
292
293            convention = 0;
294            for (i = 0; i < count; i++)
295              if (entries[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_OTHER))
296                {
297                  convention = 1;
298                  break;
299                }
300            free (entries);
301            break;
302          }
303        /* Huh? The number of ACL entries changed since the last call.
304           Repeat.  */
305        free (entries);
306      }
307  }
308
309  if (convention >= 0)
310    {
311      ace_t entries[3];
312      int ret;
313
314      if (convention)
315        {
316          /* Running on Solaris 10.  */
317          entries[0].a_type = ALLOW;
318          entries[0].a_flags = ACE_OWNER;
319          entries[0].a_who = 0; /* irrelevant */
320          entries[0].a_access_mask = (mode >> 6) & 7;
321          entries[1].a_type = ALLOW;
322          entries[1].a_flags = ACE_GROUP;
323          entries[1].a_who = 0; /* irrelevant */
324          entries[1].a_access_mask = (mode >> 3) & 7;
325          entries[2].a_type = ALLOW;
326          entries[2].a_flags = ACE_OTHER;
327          entries[2].a_who = 0;
328          entries[2].a_access_mask = mode & 7;
329        }
330      else
331        {
332          /* Running on Solaris 10 (newer version) or Solaris 11.  */
333          entries[0].a_type = ACE_ACCESS_ALLOWED_ACE_TYPE;
334          entries[0].a_flags = NEW_ACE_OWNER;
335          entries[0].a_who = 0; /* irrelevant */
336          entries[0].a_access_mask =
337            (mode & 0400 ? NEW_ACE_READ_DATA : 0)
338            | (mode & 0200 ? NEW_ACE_WRITE_DATA : 0)
339            | (mode & 0100 ? NEW_ACE_EXECUTE : 0);
340          entries[1].a_type = ACE_ACCESS_ALLOWED_ACE_TYPE;
341          entries[1].a_flags = NEW_ACE_GROUP | NEW_ACE_IDENTIFIER_GROUP;
342          entries[1].a_who = 0; /* irrelevant */
343          entries[1].a_access_mask =
344            (mode & 0040 ? NEW_ACE_READ_DATA : 0)
345            | (mode & 0020 ? NEW_ACE_WRITE_DATA : 0)
346            | (mode & 0010 ? NEW_ACE_EXECUTE : 0);
347          entries[2].a_type = ACE_ACCESS_ALLOWED_ACE_TYPE;
348          entries[2].a_flags = ACE_EVERYONE;
349          entries[2].a_who = 0;
350          entries[2].a_access_mask =
351            (mode & 0004 ? NEW_ACE_READ_DATA : 0)
352            | (mode & 0002 ? NEW_ACE_WRITE_DATA : 0)
353            | (mode & 0001 ? NEW_ACE_EXECUTE : 0);
354        }
355      if (desc != -1)
356        ret = facl (desc, ACE_SETACL,
357                    sizeof (entries) / sizeof (ace_t), entries);
358      else
359        ret = acl (name, ACE_SETACL,
360                   sizeof (entries) / sizeof (ace_t), entries);
361      if (ret < 0 && errno != EINVAL && errno != ENOTSUP)
362        {
363          if (errno == ENOSYS)
364            return chmod_or_fchmod (name, desc, mode);
365          return -1;
366        }
367    }
368#   endif
369
370  {
371    aclent_t entries[3];
372    int ret;
373
374    entries[0].a_type = USER_OBJ;
375    entries[0].a_id = 0; /* irrelevant */
376    entries[0].a_perm = (mode >> 6) & 7;
377    entries[1].a_type = GROUP_OBJ;
378    entries[1].a_id = 0; /* irrelevant */
379    entries[1].a_perm = (mode >> 3) & 7;
380    entries[2].a_type = OTHER_OBJ;
381    entries[2].a_id = 0;
382    entries[2].a_perm = mode & 7;
383
384    if (desc != -1)
385      ret = facl (desc, SETACL, sizeof (entries) / sizeof (aclent_t), entries);
386    else
387      ret = acl (name, SETACL, sizeof (entries) / sizeof (aclent_t), entries);
388    if (ret < 0)
389      {
390        if (errno == ENOSYS)
391          return chmod_or_fchmod (name, desc, mode);
392        return -1;
393      }
394  }
395
396  if (!MODE_INSIDE_ACL || (mode & (S_ISUID | S_ISGID | S_ISVTX)))
397    {
398      /* We did not call chmod so far, so the special bits have not yet
399         been set.  */
400      return chmod_or_fchmod (name, desc, mode);
401    }
402  return 0;
403
404#  endif
405
406# elif HAVE_GETACL /* HP-UX */
407
408  struct stat statbuf;
409  struct acl_entry entries[3];
410  int ret;
411
412  if (desc != -1)
413    ret = fstat (desc, &statbuf);
414  else
415    ret = stat (name, &statbuf);
416  if (ret < 0)
417    return -1;
418
419  entries[0].uid = statbuf.st_uid;
420  entries[0].gid = ACL_NSGROUP;
421  entries[0].mode = (mode >> 6) & 7;
422  entries[1].uid = ACL_NSUSER;
423  entries[1].gid = statbuf.st_gid;
424  entries[1].mode = (mode >> 3) & 7;
425  entries[2].uid = ACL_NSUSER;
426  entries[2].gid = ACL_NSGROUP;
427  entries[2].mode = mode & 7;
428
429  if (desc != -1)
430    ret = fsetacl (desc, sizeof (entries) / sizeof (struct acl_entry), entries);
431  else
432    ret = setacl (name, sizeof (entries) / sizeof (struct acl_entry), entries);
433  if (ret < 0)
434    {
435      if (errno == ENOSYS || errno == EOPNOTSUPP)
436        return chmod_or_fchmod (name, desc, mode);
437      return -1;
438    }
439
440  if (mode & (S_ISUID | S_ISGID | S_ISVTX))
441    {
442      /* We did not call chmod so far, so the special bits have not yet
443         been set.  */
444      return chmod_or_fchmod (name, desc, mode);
445    }
446  return 0;
447
448# elif HAVE_ACLX_GET && defined ACL_AIX_WIP /* AIX */
449
450  acl_type_list_t types;
451  size_t types_size = sizeof (types);
452  acl_type_t type;
453
454  if (aclx_gettypes (name, &types, &types_size) < 0
455      || types.num_entries == 0)
456    return chmod_or_fchmod (name, desc, mode);
457
458  /* XXX Do we need to clear all types of ACLs for the given file, or is it
459     sufficient to clear the first one?  */
460  type = types.entries[0];
461  if (type.u64 == ACL_AIXC)
462    {
463      union { struct acl a; char room[128]; } u;
464      int ret;
465
466      u.a.acl_len = (char *) &u.a.acl_ext[0] - (char *) &u.a; /* no entries */
467      u.a.acl_mode = mode & ~(S_IXACL | 0777);
468      u.a.u_access = (mode >> 6) & 7;
469      u.a.g_access = (mode >> 3) & 7;
470      u.a.o_access = mode & 7;
471
472      if (desc != -1)
473        ret = aclx_fput (desc, SET_ACL | SET_MODE_S_BITS,
474                         type, &u.a, u.a.acl_len, mode);
475      else
476        ret = aclx_put (name, SET_ACL | SET_MODE_S_BITS,
477                        type, &u.a, u.a.acl_len, mode);
478      if (!(ret < 0 && errno == ENOSYS))
479        return ret;
480    }
481  else if (type.u64 == ACL_NFS4)
482    {
483      union { nfs4_acl_int_t a; char room[128]; } u;
484      nfs4_ace_int_t *ace;
485      int ret;
486
487      u.a.aclVersion = NFS4_ACL_INT_STRUCT_VERSION;
488      u.a.aclEntryN = 0;
489      ace = &u.a.aclEntry[0];
490      {
491        ace->flags = ACE4_ID_SPECIAL;
492        ace->aceWho.special_whoid = ACE4_WHO_OWNER;
493        ace->aceType = ACE4_ACCESS_ALLOWED_ACE_TYPE;
494        ace->aceFlags = 0;
495        ace->aceMask =
496          (mode & 0400 ? ACE4_READ_DATA | ACE4_LIST_DIRECTORY : 0)
497          | (mode & 0200
498             ? ACE4_WRITE_DATA | ACE4_ADD_FILE | ACE4_APPEND_DATA
499               | ACE4_ADD_SUBDIRECTORY
500             : 0)
501          | (mode & 0100 ? ACE4_EXECUTE : 0);
502        ace->aceWhoString[0] = '\0';
503        ace->entryLen = (char *) &ace->aceWhoString[4] - (char *) ace;
504        ace = (nfs4_ace_int_t *) (char *) &ace->aceWhoString[4];
505        u.a.aclEntryN++;
506      }
507      {
508        ace->flags = ACE4_ID_SPECIAL;
509        ace->aceWho.special_whoid = ACE4_WHO_GROUP;
510        ace->aceType = ACE4_ACCESS_ALLOWED_ACE_TYPE;
511        ace->aceFlags = 0;
512        ace->aceMask =
513          (mode & 0040 ? ACE4_READ_DATA | ACE4_LIST_DIRECTORY : 0)
514          | (mode & 0020
515             ? ACE4_WRITE_DATA | ACE4_ADD_FILE | ACE4_APPEND_DATA
516               | ACE4_ADD_SUBDIRECTORY
517             : 0)
518          | (mode & 0010 ? ACE4_EXECUTE : 0);
519        ace->aceWhoString[0] = '\0';
520        ace->entryLen = (char *) &ace->aceWhoString[4] - (char *) ace;
521        ace = (nfs4_ace_int_t *) (char *) &ace->aceWhoString[4];
522        u.a.aclEntryN++;
523      }
524      {
525        ace->flags = ACE4_ID_SPECIAL;
526        ace->aceWho.special_whoid = ACE4_WHO_EVERYONE;
527        ace->aceType = ACE4_ACCESS_ALLOWED_ACE_TYPE;
528        ace->aceFlags = 0;
529        ace->aceMask =
530          (mode & 0004 ? ACE4_READ_DATA | ACE4_LIST_DIRECTORY : 0)
531          | (mode & 0002
532             ? ACE4_WRITE_DATA | ACE4_ADD_FILE | ACE4_APPEND_DATA
533               | ACE4_ADD_SUBDIRECTORY
534             : 0)
535          | (mode & 0001 ? ACE4_EXECUTE : 0);
536        ace->aceWhoString[0] = '\0';
537        ace->entryLen = (char *) &ace->aceWhoString[4] - (char *) ace;
538        ace = (nfs4_ace_int_t *) (char *) &ace->aceWhoString[4];
539        u.a.aclEntryN++;
540      }
541      u.a.aclLength = (char *) ace - (char *) &u.a;
542
543      if (desc != -1)
544        ret = aclx_fput (desc, SET_ACL | SET_MODE_S_BITS,
545                         type, &u.a, u.a.aclLength, mode);
546      else
547        ret = aclx_put (name, SET_ACL | SET_MODE_S_BITS,
548                        type, &u.a, u.a.aclLength, mode);
549      if (!(ret < 0 && errno == ENOSYS))
550        return ret;
551    }
552
553  return chmod_or_fchmod (name, desc, mode);
554
555# elif HAVE_STATACL /* older AIX */
556
557  union { struct acl a; char room[128]; } u;
558  int ret;
559
560  u.a.acl_len = (char *) &u.a.acl_ext[0] - (char *) &u.a; /* no entries */
561  u.a.acl_mode = mode & ~(S_IXACL | 0777);
562  u.a.u_access = (mode >> 6) & 7;
563  u.a.g_access = (mode >> 3) & 7;
564  u.a.o_access = mode & 7;
565
566  if (desc != -1)
567    ret = fchacl (desc, &u.a, u.a.acl_len);
568  else
569    ret = chacl (name, &u.a, u.a.acl_len);
570
571  if (ret < 0 && errno == ENOSYS)
572    return chmod_or_fchmod (name, desc, mode);
573
574  return ret;
575
576# else /* Unknown flavor of ACLs */
577  return chmod_or_fchmod (name, desc, mode);
578# endif
579#else /* !USE_ACL */
580  return chmod_or_fchmod (name, desc, mode);
581#endif
582}
583
584/* As with qset_acl, but also output a diagnostic on failure.  */
585
586int
587set_acl (char const *name, int desc, mode_t mode)
588{
589  int r = qset_acl (name, desc, mode);
590  if (r != 0)
591    error (0, errno, _("setting permissions for %s"), quote (name));
592  return r;
593}
594