Improve the check for departures from C89, and fix the departures
[platform/upstream/coreutils.git] / src / chown-core.c
1 /* chown-core.c -- core functions for changing ownership.
2    Copyright (C) 2000, 2002, 2003, 2004, 2005, 2006 Free Software Foundation.
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software Foundation,
16    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
17
18 /* Extracted from chown.c/chgrp.c and librarified by Jim Meyering.  */
19
20 #include <config.h>
21 #include <stdio.h>
22 #include <sys/types.h>
23 #include <pwd.h>
24 #include <grp.h>
25
26 #include "system.h"
27 #include "chown-core.h"
28 #include "error.h"
29 #include "inttostr.h"
30 #include "openat.h"
31 #include "quote.h"
32 #include "root-dev-ino.h"
33 #include "xfts.h"
34
35 enum RCH_status
36   {
37     /* we called fchown and close, and both succeeded */
38     RC_ok = 2,
39
40     /* required_uid and/or required_gid are specified, but don't match */
41     RC_excluded,
42
43     /* SAME_INODE check failed */
44     RC_inode_changed,
45
46     /* open/fchown isn't needed, isn't safe, or doesn't work due to
47        permissions problems; fall back on chown */
48     RC_do_ordinary_chown,
49
50     /* open, fstat, fchown, or close failed */
51     RC_error
52   };
53
54 extern void
55 chopt_init (struct Chown_option *chopt)
56 {
57   chopt->verbosity = V_off;
58   chopt->root_dev_ino = NULL;
59   chopt->affect_symlink_referent = true;
60   chopt->recurse = false;
61   chopt->force_silent = false;
62   chopt->user_name = NULL;
63   chopt->group_name = NULL;
64 }
65
66 extern void
67 chopt_free (struct Chown_option *chopt ATTRIBUTE_UNUSED)
68 {
69   /* Deliberately do not free chopt->user_name or ->group_name.
70      They're not always allocated.  */
71 }
72
73 /* Convert the numeric group-id, GID, to a string stored in xmalloc'd memory,
74    and return it.  If there's no corresponding group name, use the decimal
75    representation of the ID.  */
76
77 extern char *
78 gid_to_name (gid_t gid)
79 {
80   char buf[INT_BUFSIZE_BOUND (intmax_t)];
81   struct group *grp = getgrgid (gid);
82   return xstrdup (grp ? grp->gr_name
83                   : TYPE_SIGNED (gid_t) ? imaxtostr (gid, buf)
84                   : umaxtostr (gid, buf));
85 }
86
87 /* Convert the numeric user-id, UID, to a string stored in xmalloc'd memory,
88    and return it.  If there's no corresponding user name, use the decimal
89    representation of the ID.  */
90
91 extern char *
92 uid_to_name (uid_t uid)
93 {
94   char buf[INT_BUFSIZE_BOUND (intmax_t)];
95   struct passwd *pwd = getpwuid (uid);
96   return xstrdup (pwd ? pwd->pw_name
97                   : TYPE_SIGNED (uid_t) ? imaxtostr (uid, buf)
98                   : umaxtostr (uid, buf));
99 }
100
101 /* Tell the user how/if the user and group of FILE have been changed.
102    If USER is NULL, give the group-oriented messages.
103    CHANGED describes what (if anything) has happened. */
104
105 static void
106 describe_change (const char *file, enum Change_status changed,
107                  char const *user, char const *group)
108 {
109   const char *fmt;
110   char const *spec;
111   char *spec_allocated = NULL;
112
113   if (changed == CH_NOT_APPLIED)
114     {
115       printf (_("neither symbolic link %s nor referent has been changed\n"),
116               quote (file));
117       return;
118     }
119
120   if (user)
121     {
122       if (group)
123         {
124           spec_allocated = xmalloc (strlen (user) + 1 + strlen (group) + 1);
125           stpcpy (stpcpy (stpcpy (spec_allocated, user), ":"), group);
126           spec = spec_allocated;
127         }
128       else
129         {
130           spec = user;
131         }
132     }
133   else
134     {
135       spec = group;
136     }
137
138   switch (changed)
139     {
140     case CH_SUCCEEDED:
141       fmt = (user ? _("changed ownership of %s to %s\n")
142              : group ? _("changed group of %s to %s\n")
143              : _("no change to ownership of %s\n"));
144       break;
145     case CH_FAILED:
146       fmt = (user ? _("failed to change ownership of %s to %s\n")
147              : group ? _("failed to change group of %s to %s\n")
148              : _("failed to change ownership of %s\n"));
149       break;
150     case CH_NO_CHANGE_REQUESTED:
151       fmt = (user ? _("ownership of %s retained as %s\n")
152              : group ? _("group of %s retained as %s\n")
153              : _("ownership of %s retained\n"));
154       break;
155     default:
156       abort ();
157     }
158
159   printf (fmt, quote (file), spec);
160
161   free (spec_allocated);
162 }
163
164 /* Change the owner and/or group of the FILE to UID and/or GID (safely)
165    only if REQUIRED_UID and REQUIRED_GID match the owner and group IDs
166    of FILE.  ORIG_ST must be the result of `stat'ing FILE.
167
168    The `safely' part above means that we can't simply use chown(2),
169    since FILE might be replaced with some other file between the time
170    of the preceding stat/lstat and this chown call.  So here we open
171    FILE and do everything else via the resulting file descriptor.
172    We first call fstat and verify that the dev/inode match those from
173    the preceding stat call, and only then, if appropriate (given the
174    required_uid and required_gid constraints) do we call fchown.
175
176    Return RC_do_ordinary_chown if we can't open FILE, or if FILE is a
177    special file that might have undesirable side effects when opening.
178    In this case the caller can use the less-safe ordinary chown.
179
180    Return one of the RCH_status values.  */
181
182 static enum RCH_status
183 restricted_chown (int cwd_fd, char const *file,
184                   struct stat const *orig_st,
185                   uid_t uid, gid_t gid,
186                   uid_t required_uid, gid_t required_gid)
187 {
188   enum RCH_status status = RC_ok;
189   struct stat st;
190   int open_flags = O_NONBLOCK | O_NOCTTY;
191   int fd;
192
193   if (required_uid == (uid_t) -1 && required_gid == (gid_t) -1)
194     return RC_do_ordinary_chown;
195
196   if (! S_ISREG (orig_st->st_mode))
197     {
198       if (S_ISDIR (orig_st->st_mode))
199         open_flags |= O_DIRECTORY;
200       else
201         return RC_do_ordinary_chown;
202     }
203
204   fd = openat (cwd_fd, file, O_RDONLY | open_flags);
205   if (! (0 <= fd
206          || (errno == EACCES && S_ISREG (orig_st->st_mode)
207              && 0 <= (fd = openat (cwd_fd, file, O_WRONLY | open_flags)))))
208     return (errno == EACCES ? RC_do_ordinary_chown : RC_error);
209
210   if (fstat (fd, &st) != 0)
211     status = RC_error;
212   else if (! SAME_INODE (*orig_st, st))
213     status = RC_inode_changed;
214   else if ((required_uid == (uid_t) -1 || required_uid == st.st_uid)
215            && (required_gid == (gid_t) -1 || required_gid == st.st_gid))
216     {
217       if (fchown (fd, uid, gid) == 0)
218         {
219           status = (close (fd) == 0
220                     ? RC_ok : RC_error);
221           return status;
222         }
223       else
224         {
225           status = RC_error;
226         }
227     }
228
229   { /* FIXME: remove these curly braces when we assume C99.  */
230     int saved_errno = errno;
231     close (fd);
232     errno = saved_errno;
233     return status;
234   }
235 }
236
237 /* Change the owner and/or group of the file specified by FTS and ENT
238    to UID and/or GID as appropriate.
239    If REQUIRED_UID is not -1, then skip files with any other user ID.
240    If REQUIRED_GID is not -1, then skip files with any other group ID.
241    CHOPT specifies additional options.
242    Return true if successful.  */
243 static bool
244 change_file_owner (FTS *fts, FTSENT *ent,
245                    uid_t uid, gid_t gid,
246                    uid_t required_uid, gid_t required_gid,
247                    struct Chown_option const *chopt)
248 {
249   char const *file_full_name = ent->fts_path;
250   char const *file = ent->fts_accpath;
251   struct stat const *file_stats;
252   struct stat stat_buf;
253   bool ok = true;
254   bool do_chown;
255   bool symlink_changed = true;
256
257   switch (ent->fts_info)
258     {
259     case FTS_D:
260       if (chopt->recurse)
261         return true;
262       break;
263
264     case FTS_DP:
265       if (! chopt->recurse)
266         return true;
267       break;
268
269     case FTS_NS:
270       /* For a top-level file or directory, this FTS_NS (stat failed)
271          indicator is determined at the time of the initial fts_open call.
272          With programs like chmod, chown, and chgrp, that modify
273          permissions, it is possible that the file in question is
274          accessible when control reaches this point.  So, if this is
275          the first time we've seen the FTS_NS for this file, tell
276          fts_read to stat it "again".  */
277       if (ent->fts_level == 0 && ent->fts_number == 0)
278         {
279           ent->fts_number = 1;
280           fts_set (fts, ent, FTS_AGAIN);
281           return true;
282         }
283       error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
284       ok = false;
285       break;
286
287     case FTS_ERR:
288       error (0, ent->fts_errno, _("%s"), quote (file_full_name));
289       ok = false;
290       break;
291
292     case FTS_DNR:
293       error (0, ent->fts_errno, _("cannot read directory %s"),
294              quote (file_full_name));
295       ok = false;
296       break;
297
298     default:
299       break;
300     }
301
302   if (!ok)
303     {
304       do_chown = false;
305       file_stats = NULL;
306     }
307   else if (required_uid == (uid_t) -1 && required_gid == (gid_t) -1
308            && chopt->verbosity == V_off
309            && ! chopt->root_dev_ino
310            && ! chopt->affect_symlink_referent)
311     {
312       do_chown = true;
313       file_stats = ent->fts_statp;
314     }
315   else
316     {
317       file_stats = ent->fts_statp;
318
319       /* If this is a symlink and we're dereferencing them,
320          stat it to get info on the referent.  */
321       if (chopt->affect_symlink_referent && S_ISLNK (file_stats->st_mode))
322         {
323           if (fstatat (fts->fts_cwd_fd, file, &stat_buf, 0) != 0)
324             {
325               error (0, errno, _("cannot dereference %s"),
326                      quote (file_full_name));
327               ok = false;
328             }
329
330           file_stats = &stat_buf;
331         }
332
333       do_chown = (ok
334                   && (required_uid == (uid_t) -1
335                       || required_uid == file_stats->st_uid)
336                   && (required_gid == (gid_t) -1
337                       || required_gid == file_stats->st_gid));
338     }
339
340   if (do_chown
341       /* With FTS_NOSTAT, file_stats is valid only for directories.
342          Don't need to check for FTS_D, since it is handled above,
343          and same for FTS_DNR, since then do_chown is false.  */
344       && (ent->fts_info == FTS_DP || ent->fts_info == FTS_DC)
345       && ROOT_DEV_INO_CHECK (chopt->root_dev_ino, file_stats))
346     {
347       ROOT_DEV_INO_WARN (file_full_name);
348       ok = do_chown = false;
349     }
350
351   if (do_chown)
352     {
353       if ( ! chopt->affect_symlink_referent)
354         {
355           ok = (lchownat (fts->fts_cwd_fd, file, uid, gid) == 0);
356
357           /* Ignore any error due to lack of support; POSIX requires
358              this behavior for top-level symbolic links with -h, and
359              implies that it's required for all symbolic links.  */
360           if (!ok && errno == EOPNOTSUPP)
361             {
362               ok = true;
363               symlink_changed = false;
364             }
365         }
366       else
367         {
368           /* If possible, avoid a race condition with --from=O:G and without the
369              (-h) --no-dereference option.  If fts's stat call determined
370              that the uid/gid of FILE matched the --from=O:G-selected
371              owner and group IDs, blindly using chown(2) here could lead
372              chown(1) or chgrp(1) mistakenly to dereference a *symlink*
373              to an arbitrary file that an attacker had moved into the
374              place of FILE during the window between the stat and
375              chown(2) calls.  If FILE is a regular file or a directory
376              that can be opened, this race condition can be avoided safely.  */
377
378           enum RCH_status err
379             = restricted_chown (fts->fts_cwd_fd, file, file_stats, uid, gid,
380                                 required_uid, required_gid);
381           switch (err)
382             {
383             case RC_ok:
384               break;
385
386             case RC_do_ordinary_chown:
387               ok = (chownat (fts->fts_cwd_fd, file, uid, gid) == 0);
388               break;
389
390             case RC_error:
391               ok = false;
392               break;
393
394             case RC_inode_changed:
395               /* FIXME: give a diagnostic in this case?  */
396             case RC_excluded:
397               do_chown = false;
398               ok = false;
399               break;
400
401             default:
402               abort ();
403             }
404         }
405
406       /* On some systems (e.g., Linux-2.4.x),
407          the chown function resets the `special' permission bits.
408          Do *not* restore those bits;  doing so would open a window in
409          which a malicious user, M, could subvert a chown command run
410          by some other user and operating on files in a directory
411          where M has write access.  */
412
413       if (do_chown && !ok && ! chopt->force_silent)
414         error (0, errno, (uid != (uid_t) -1
415                           ? _("changing ownership of %s")
416                           : _("changing group of %s")),
417                quote (file_full_name));
418     }
419
420   if (chopt->verbosity != V_off)
421     {
422       bool changed =
423         ((do_chown & ok & symlink_changed)
424          && ! ((uid == (uid_t) -1 || uid == file_stats->st_uid)
425                && (gid == (gid_t) -1 || gid == file_stats->st_gid)));
426
427       if (changed || chopt->verbosity == V_high)
428         {
429           enum Change_status ch_status =
430             (!ok ? CH_FAILED
431              : !symlink_changed ? CH_NOT_APPLIED
432              : !changed ? CH_NO_CHANGE_REQUESTED
433              : CH_SUCCEEDED);
434           describe_change (file_full_name, ch_status,
435                            chopt->user_name, chopt->group_name);
436         }
437     }
438
439   if ( ! chopt->recurse)
440     fts_set (fts, ent, FTS_SKIP);
441
442   return ok;
443 }
444
445 /* Change the owner and/or group of the specified FILES.
446    BIT_FLAGS specifies how to treat each symlink-to-directory
447    that is encountered during a recursive traversal.
448    CHOPT specifies additional options.
449    If UID is not -1, then change the owner id of each file to UID.
450    If GID is not -1, then change the group id of each file to GID.
451    If REQUIRED_UID and/or REQUIRED_GID is not -1, then change only
452    files with user ID and group ID that match the non-(-1) value(s).
453    Return true if successful.  */
454 extern bool
455 chown_files (char **files, int bit_flags,
456              uid_t uid, gid_t gid,
457              uid_t required_uid, gid_t required_gid,
458              struct Chown_option const *chopt)
459 {
460   bool ok = true;
461
462   /* Use lstat and stat only if they're needed.  */
463   int stat_flags = ((required_uid != (uid_t) -1 || required_gid != (gid_t) -1
464                      || chopt->affect_symlink_referent
465                      || chopt->verbosity != V_off)
466                     ? 0
467                     : FTS_NOSTAT);
468
469   FTS *fts = xfts_open (files, bit_flags | stat_flags, NULL);
470
471   while (1)
472     {
473       FTSENT *ent;
474
475       ent = fts_read (fts);
476       if (ent == NULL)
477         {
478           if (errno != 0)
479             {
480               /* FIXME: try to give a better message  */
481               error (0, errno, _("fts_read failed"));
482               ok = false;
483             }
484           break;
485         }
486
487       ok &= change_file_owner (fts, ent, uid, gid,
488                                required_uid, required_gid, chopt);
489     }
490
491   /* Ignore failure, since the only way it can do so is in failing to
492      return to the original directory, and since we're about to exit,
493      that doesn't matter.  */
494   fts_close (fts);
495
496   return ok;
497 }