Home | History | Annotate | Download | only in lib
      1 /* set-mode-acl.c - set access control list equivalent to a mode
      2 
      3    Copyright (C) 2002-2003, 2005-2009 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 
     36 int
     37 chmod_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 
     53 int
     54 qset_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)
    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 && 0 /* AIX */
    449 
    450   /* TODO: use aclx_fput or aclx_put, respectively */
    451 
    452 # elif HAVE_STATACL /* older AIX */
    453 
    454   union { struct acl a; char room[128]; } u;
    455   int ret;
    456 
    457   u.a.acl_len = (char *) &u.a.acl_ext[0] - (char *) &u.a; /* no entries */
    458   u.a.acl_mode = mode & ~(S_IXACL | 0777);
    459   u.a.u_access = (mode >> 6) & 7;
    460   u.a.g_access = (mode >> 3) & 7;
    461   u.a.o_access = mode & 7;
    462 
    463   if (desc != -1)
    464     ret = fchacl (desc, &u.a, u.a.acl_len);
    465   else
    466     ret = chacl (name, &u.a, u.a.acl_len);
    467 
    468   if (ret < 0 && errno == ENOSYS)
    469     return chmod_or_fchmod (name, desc, mode);
    470 
    471   return ret;
    472 
    473 # else /* Unknown flavor of ACLs */
    474   return chmod_or_fchmod (name, desc, mode);
    475 # endif
    476 #else /* !USE_ACL */
    477   return chmod_or_fchmod (name, desc, mode);
    478 #endif
    479 }
    480 
    481 /* As with qset_acl, but also output a diagnostic on failure.  */
    482 
    483 int
    484 set_acl (char const *name, int desc, mode_t mode)
    485 {
    486   int r = qset_acl (name, desc, mode);
    487   if (r != 0)
    488     error (0, errno, _("setting permissions for %s"), quote (name));
    489   return r;
    490 }
    491