b5c1ee94498b08d54cf06505932829c08d04a2f6
[gnulib.git] / lib / acl.c
1 /* acl.c - access control lists
2
3    Copyright (C) 2002, 2003, 2005 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 2, or (at your option)
8    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, write to the Free Software Foundation,
17    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
19    Written by Paul Eggert and Andreas Gruenbacher.  */
20
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 #include <stdbool.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #ifndef S_ISLNK
31 # define S_ISLNK(Mode) 0
32 #endif
33
34 #ifdef HAVE_ACL_LIBACL_H
35 # include <acl/libacl.h>
36 #endif
37
38 #include "acl.h"
39 #include "error.h"
40 #include "quote.h"
41
42 #include <errno.h>
43 #ifndef ENOSYS
44 # define ENOSYS (-1)
45 #endif
46 #ifndef ENOTSUP
47 # define ENOTSUP (-1)
48 #endif
49
50 #if ENABLE_NLS
51 # include <libintl.h>
52 # define _(Text) gettext (Text)
53 #else
54 # define _(Text) Text
55 #endif
56
57 #ifndef HAVE_FCHMOD
58 # define HAVE_FCHMOD false
59 # define fchmod(fd, mode) (-1)
60 #endif
61
62 /* POSIX 1003.1e (draft 17) */
63 #ifndef HAVE_ACL_GET_FD
64 # define HAVE_ACL_GET_FD false
65 # define acl_get_fd(fd) (NULL)
66 #endif
67
68 /* POSIX 1003.1e (draft 17) */
69 #ifndef HAVE_ACL_SET_FD
70 # define HAVE_ACL_SET_FD false
71 # define acl_set_fd(fd, acl) (-1)
72 #endif
73
74 /* Linux-specific */
75 #ifndef HAVE_ACL_EXTENDED_FILE
76 # define HAVE_ACL_EXTENDED_FILE false
77 # define acl_extended_file(name) (-1)
78 #endif
79
80 /* Linux-specific */
81 #ifndef HAVE_ACL_FROM_MODE
82 # define HAVE_ACL_FROM_MODE false
83 # define acl_from_mode(mode) (NULL)
84 #endif
85
86 /* We detect presence of POSIX 1003.1e (draft 17 -- abandoned) support
87    by checking for HAVE_ACL_GET_FILE, HAVE_ACL_SET_FILE, and HAVE_ACL_FREE.
88    Systems that have acl_get_file, acl_set_file, and acl_free must also
89    have acl_to_text, acl_from_text, and acl_delete_def_file (all defined
90    in the draft); systems that don't would hit #error statements here.  */
91
92 #if USE_ACL && HAVE_ACL_GET_FILE && !HAVE_ACL_ENTRIES
93 # ifndef HAVE_ACL_TO_TEXT
94 #  error Must have acl_to_text (see POSIX 1003.1e draft 17).
95 # endif
96
97 /* Return the number of entries in ACL. Linux implements acl_entries
98    as a more efficient extension than using this workaround.  */
99
100 static int
101 acl_entries (acl_t acl)
102 {
103   char *text = acl_to_text (acl, NULL), *t;
104   int entries;
105   if (text == NULL)
106     return -1;
107   for (entries = 0, t = text; ; t++, entries++) {
108     t = strchr (t, '\n');
109     if (t == NULL)
110       break;
111   }
112   acl_free (text);
113   return entries;
114 }
115 #endif
116
117 /* If DESC is a valid file descriptor use fchmod to change the
118    file's mode to MODE on systems that have fchown. On systems
119    that don't have fchown and if DESC is invalid, use chown on
120    NAME instead.  */
121
122 int
123 chmod_or_fchmod (const char *name, int desc, mode_t mode)
124 {
125   if (HAVE_FCHMOD && desc != -1)
126     return fchmod (desc, mode);
127   else
128     return chmod (name, mode);
129 }
130
131 /* Return 1 if NAME has a nontrivial access control list, 0 if
132    NAME only has no or a base access control list, and -1 on
133    error.  SB must be set to the stat buffer of FILE.  */
134
135 int
136 file_has_acl (char const *name, struct stat const *sb)
137 {
138 #if USE_ACL && HAVE_ACL && defined GETACLCNT
139   /* This implementation should work on recent-enough versions of HP-UX,
140      Solaris, and Unixware.  */
141
142 # ifndef MIN_ACL_ENTRIES
143 #  define MIN_ACL_ENTRIES 4
144 # endif
145
146   if (! S_ISLNK (sb->st_mode))
147     {
148       int n = acl (name, GETACLCNT, 0, NULL);
149       return n < 0 ? (errno == ENOSYS ? 0 : -1) : (MIN_ACL_ENTRIES < n);
150     }
151 #elif USE_ACL && HAVE_ACL_GET_FILE && HAVE_ACL_FREE
152   /* POSIX 1003.1e (draft 17 -- abandoned) specific version.  */
153
154   if (! S_ISLNK (sb->st_mode))
155     {
156       int ret;
157
158       if (HAVE_ACL_EXTENDED_FILE)
159         ret = acl_extended_file (name);
160       else
161         {
162           acl_t acl = acl_get_file (name, ACL_TYPE_ACCESS);
163           if (acl)
164             {
165               ret = (3 < acl_entries (acl));
166               acl_free (acl);
167               if (ret == 0 && S_ISDIR (sb->st_mode))
168                 {
169                   acl = acl_get_file (name, ACL_TYPE_DEFAULT);
170                   if (acl)
171                     {
172                       ret = (0 < acl_entries (acl));
173                       acl_free (acl);
174                     }
175                   else
176                     ret = -1;
177                 }
178             }
179           else
180             ret = -1;
181         }
182       if (ret < 0)
183         return (errno == ENOSYS || errno == ENOTSUP) ? 0 : -1;
184       return ret;
185     }
186 #endif
187
188   /* FIXME: Add support for AIX, Irix, and Tru64.  Please see Samba's
189      source/lib/sysacls.c file for fix-related ideas.  */
190
191   return 0;
192 }
193
194 /* Copy access control lists from one file to another. If SOURCE_DESC is
195    a valid file descriptor, use file descriptor operations, else use
196    filename based operations on SRC_NAME. Likewise for DEST_DESC and
197    DEST_NAME.
198    If access control lists are not available, fchmod the target file to
199    MODE.  Also sets the non-permission bits of the destination file
200    (S_ISUID, S_ISGID, S_ISVTX) to those from MODE if any are set.
201    System call return value semantics.  */
202
203 int
204 copy_acl (const char *src_name, int source_desc, const char *dst_name,
205           int dest_desc, mode_t mode)
206 {
207   int ret;
208
209 #if USE_ACL && HAVE_ACL_GET_FILE && HAVE_ACL_SET_FILE && HAVE_ACL_FREE
210   /* POSIX 1003.1e (draft 17 -- abandoned) specific version.  */
211
212   acl_t acl;
213   if (HAVE_ACL_GET_FD && source_desc != -1)
214     acl = acl_get_fd (source_desc);
215   else
216     acl = acl_get_file (src_name, ACL_TYPE_ACCESS);
217   if (acl == NULL)
218     {
219       if (errno == ENOSYS || errno == ENOTSUP)
220         return set_acl (dst_name, dest_desc, mode);
221       else
222         {
223           error (0, errno, "%s", quote (src_name));
224           return -1;
225         }
226     }
227
228   if (HAVE_ACL_SET_FD && dest_desc != -1)
229     ret = acl_set_fd (dest_desc, acl);
230   else
231     ret = acl_set_file (dst_name, ACL_TYPE_ACCESS, acl);
232   if (ret != 0)
233     {
234       int saved_errno = errno;
235
236       if (errno == ENOSYS || errno == ENOTSUP)
237         {
238           int n = acl_entries (acl);
239
240           acl_free (acl);
241           if (n == 3)
242             {
243               if (chmod_or_fchmod (dst_name, dest_desc, mode) != 0)
244                 saved_errno = errno;
245               else
246                 return 0;
247             }
248           else
249             chmod_or_fchmod (dst_name, dest_desc, mode);
250         }
251       else
252         {
253           acl_free (acl);
254           chmod_or_fchmod (dst_name, dest_desc, mode);
255         }
256       error (0, saved_errno, _("preserving permissions for %s"),
257              quote (dst_name));
258       return -1;
259     }
260   else
261     acl_free (acl);
262
263   if (mode & (S_ISUID | S_ISGID | S_ISVTX))
264     {
265       /* We did not call chmod so far, so the special bits have not yet
266          been set.  */
267
268       if (chmod_or_fchmod (dst_name, dest_desc, mode) != 0)
269         {
270           error (0, errno, _("preserving permissions for %s"),
271                  quote (dst_name));
272           return -1;
273         }
274     }
275
276   if (S_ISDIR (mode))
277     {
278       acl = acl_get_file (src_name, ACL_TYPE_DEFAULT);
279       if (acl == NULL)
280         {
281           error (0, errno, "%s", quote (src_name));
282           return -1;
283         }
284
285       if (acl_set_file (dst_name, ACL_TYPE_DEFAULT, acl))
286         {
287           error (0, errno, _("preserving permissions for %s"),
288                  quote (dst_name));
289           acl_free (acl);
290           return -1;
291         }
292       else
293         acl_free (acl);
294     }
295   return 0;
296 #else
297   ret = chmod_or_fchmod (dst_name, dest_desc, mode);
298   if (ret != 0)
299     error (0, errno, _("preserving permissions for %s"), quote (dst_name));
300   return ret;
301 #endif
302 }
303
304 /* Set the access control lists of a file. If DESC is a valid file
305    descriptor, use file descriptor operations where available, else use
306    filename based operations on NAME.  If access control lists are not
307    available, fchmod the target file to MODE.  Also sets the
308    non-permission bits of the destination file (S_ISUID, S_ISGID, S_ISVTX)
309    to those from MODE if any are set.  System call return value
310    semantics.  */
311
312 int
313 set_acl (char const *name, int desc, mode_t mode)
314 {
315 #if USE_ACL && HAVE_ACL_SET_FILE && HAVE_ACL_FREE
316   /* POSIX 1003.1e draft 17 (abandoned) specific version.  */
317
318   /* We must also have have_acl_from_text and acl_delete_def_file.
319      (acl_delete_def_file could be emulated with acl_init followed
320       by acl_set_file, but acl_set_file with an empty acl is
321       unspecified.)  */
322
323 # ifndef HAVE_ACL_FROM_TEXT
324 #  error Must have acl_from_text (see POSIX 1003.1e draft 17).
325 # endif
326 # ifndef HAVE_ACL_DELETE_DEF_FILE
327 #  error Must have acl_delete_def_file (see POSIX 1003.1e draft 17).
328 # endif
329
330   acl_t acl;
331   int ret;
332
333   if (HAVE_ACL_FROM_MODE)
334     {
335       acl = acl_from_mode (mode);
336       if (!acl)
337         {
338           error (0, errno, "%s", quote (name));
339           return -1;
340         }
341     }
342   else
343     {
344       char acl_text[] = "u::---,g::---,o::---";
345
346       if (mode & S_IRUSR) acl_text[ 3] = 'r';
347       if (mode & S_IWUSR) acl_text[ 4] = 'w';
348       if (mode & S_IXUSR) acl_text[ 5] = 'x';
349       if (mode & S_IRGRP) acl_text[10] = 'r';
350       if (mode & S_IWGRP) acl_text[11] = 'w';
351       if (mode & S_IXGRP) acl_text[12] = 'x';
352       if (mode & S_IROTH) acl_text[17] = 'r';
353       if (mode & S_IWOTH) acl_text[18] = 'w';
354       if (mode & S_IXOTH) acl_text[19] = 'x';
355
356       acl = acl_from_text (acl_text);
357       if (!acl)
358         {
359           error (0, errno, "%s", quote (name));
360           return -1;
361         }
362     }
363   if (HAVE_ACL_SET_FD && desc != -1)
364     ret = acl_set_fd (desc, acl);
365   else
366     ret = acl_set_file (name, ACL_TYPE_ACCESS, acl);
367   if (ret != 0)
368     {
369       int saved_errno = errno;
370       acl_free (acl);
371
372       if (errno == ENOTSUP || errno == ENOSYS)
373         {
374           if (chmod_or_fchmod (name, desc, mode) != 0)
375             saved_errno = errno;
376           else
377             return 0;
378         }
379       error (0, saved_errno, _("setting permissions for %s"), quote (name));
380       return -1;
381     }
382   else
383     acl_free (acl);
384
385   if (S_ISDIR (mode) && acl_delete_def_file (name))
386     {
387       error (0, errno, _("setting permissions for %s"), quote (name));
388       return -1;
389     }
390
391   if (mode & (S_ISUID | S_ISGID | S_ISVTX))
392     {
393       /* We did not call chmod so far, so the special bits have not yet
394          been set.  */
395
396       if (chmod_or_fchmod (name, desc, mode))
397         {
398           error (0, errno, _("preserving permissions for %s"), quote (name));
399           return -1;
400         }
401     }
402   return 0;
403 #else
404    int ret = chmod_or_fchmod (name, desc, mode);
405    if (ret)
406      error (0, errno, _("setting permissions for %s"), quote (name));
407    return ret;
408 #endif
409 }