• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
chmod_or_fchmod(const char * name,int desc,mode_t mode)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
qset_acl(char const * name,int desc,mode_t mode)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
set_acl(char const * name,int desc,mode_t mode)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