*** empty log message ***
[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       error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
271       ok = false;
272       break;
273
274     case FTS_ERR:
275       error (0, ent->fts_errno, _("%s"), quote (file_full_name));
276       ok = false;
277       break;
278
279     case FTS_DNR:
280       error (0, ent->fts_errno, _("cannot read directory %s"),
281              quote (file_full_name));
282       ok = false;
283       break;
284
285     default:
286       break;
287     }
288
289   if (!ok)
290     {
291       do_chown = false;
292       file_stats = NULL;
293     }
294   else if (required_uid == (uid_t) -1 && required_gid == (gid_t) -1
295            && chopt->verbosity == V_off && ! chopt->root_dev_ino)
296     {
297       do_chown = true;
298       file_stats = ent->fts_statp;
299     }
300   else
301     {
302       file_stats = ent->fts_statp;
303
304       /* If this is a symlink and we're dereferencing them,
305          stat it to get info on the referent.  */
306       if (S_ISLNK (file_stats->st_mode) && chopt->affect_symlink_referent)
307         {
308           if (stat (file, &stat_buf) != 0)
309             {
310               error (0, errno, _("cannot dereference %s"),
311                      quote (file_full_name));
312               ok = false;
313             }
314
315           file_stats = &stat_buf;
316         }
317
318       do_chown = (ok
319                   && (required_uid == (uid_t) -1
320                       || required_uid == file_stats->st_uid)
321                   && (required_gid == (gid_t) -1
322                       || required_gid == file_stats->st_gid));
323     }
324
325   if (do_chown && ROOT_DEV_INO_CHECK (chopt->root_dev_ino, file_stats))
326     {
327       ROOT_DEV_INO_WARN (file_full_name);
328       ok = do_chown = false;
329     }
330
331   if (do_chown)
332     {
333       if ( ! chopt->affect_symlink_referent)
334         {
335           ok = (lchownat (fts->fts_cwd_fd, file, uid, gid) == 0);
336
337           /* Ignore any error due to lack of support; POSIX requires
338              this behavior for top-level symbolic links with -h, and
339              implies that it's required for all symbolic links.  */
340           if (!ok && errno == EOPNOTSUPP)
341             {
342               ok = true;
343               symlink_changed = false;
344             }
345         }
346       else
347         {
348           /* If possible, avoid a race condition with --from=O:G and without the
349              (-h) --no-dereference option.  If fts's stat call determined
350              that the uid/gid of FILE matched the --from=O:G-selected
351              owner and group IDs, blindly using chown(2) here could lead
352              chown(1) or chgrp(1) mistakenly to dereference a *symlink*
353              to an arbitrary file that an attacker had moved into the
354              place of FILE during the window between the stat and
355              chown(2) calls.  If FILE is a regular file or a directory
356              that can be opened, this race condition can be avoided safely.  */
357
358           enum RCH_status err
359             = restricted_chown (fts->fts_cwd_fd, file, file_stats, uid, gid,
360                                 required_uid, required_gid);
361           switch (err)
362             {
363             case RC_ok:
364               break;
365
366             case RC_do_ordinary_chown:
367               ok = (chownat (fts->fts_cwd_fd, file, uid, gid) == 0);
368               break;
369
370             case RC_error:
371               ok = false;
372               break;
373
374             case RC_inode_changed:
375               /* FIXME: give a diagnostic in this case?  */
376             case RC_excluded:
377               do_chown = false;
378               ok = false;
379               break;
380
381             default:
382               abort ();
383             }
384         }
385
386       /* On some systems (e.g., Linux-2.4.x),
387          the chown function resets the `special' permission bits.
388          Do *not* restore those bits;  doing so would open a window in
389          which a malicious user, M, could subvert a chown command run
390          by some other user and operating on files in a directory
391          where M has write access.  */
392
393       if (do_chown && !ok && ! chopt->force_silent)
394         error (0, errno, (uid != (uid_t) -1
395                           ? _("changing ownership of %s")
396                           : _("changing group of %s")),
397                quote (file_full_name));
398     }
399
400   if (chopt->verbosity != V_off)
401     {
402       bool changed =
403         ((do_chown & ok & symlink_changed)
404          && ! ((uid == (uid_t) -1 || uid == file_stats->st_uid)
405                && (gid == (gid_t) -1 || gid == file_stats->st_gid)));
406
407       if (changed || chopt->verbosity == V_high)
408         {
409           enum Change_status ch_status =
410             (!ok ? CH_FAILED
411              : !symlink_changed ? CH_NOT_APPLIED
412              : !changed ? CH_NO_CHANGE_REQUESTED
413              : CH_SUCCEEDED);
414           describe_change (file_full_name, ch_status,
415                            chopt->user_name, chopt->group_name);
416         }
417     }
418
419   if ( ! chopt->recurse)
420     fts_set (fts, ent, FTS_SKIP);
421
422   return ok;
423 }
424
425 /* Change the owner and/or group of the specified FILES.
426    BIT_FLAGS specifies how to treat each symlink-to-directory
427    that is encountered during a recursive traversal.
428    CHOPT specifies additional options.
429    If UID is not -1, then change the owner id of each file to UID.
430    If GID is not -1, then change the group id of each file to GID.
431    If REQUIRED_UID and/or REQUIRED_GID is not -1, then change only
432    files with user ID and group ID that match the non-(-1) value(s).
433    Return true if successful.  */
434 extern bool
435 chown_files (char **files, int bit_flags,
436              uid_t uid, gid_t gid,
437              uid_t required_uid, gid_t required_gid,
438              struct Chown_option const *chopt)
439 {
440   bool ok = true;
441
442   /* Use lstat and stat only if they're needed.  */
443   int stat_flags = ((required_uid != (uid_t) -1 || required_gid != (gid_t) -1
444                      || chopt->verbosity != V_off || chopt->root_dev_ino)
445                     ? 0
446                     : FTS_NOSTAT);
447
448   FTS *fts = xfts_open (files, bit_flags | stat_flags, NULL);
449
450   while (1)
451     {
452       FTSENT *ent;
453
454       ent = fts_read (fts);
455       if (ent == NULL)
456         {
457           if (errno != 0)
458             {
459               /* FIXME: try to give a better message  */
460               error (0, errno, _("fts_read failed"));
461               ok = false;
462             }
463           break;
464         }
465
466       ok &= change_file_owner (fts, ent, uid, gid,
467                                required_uid, required_gid, chopt);
468     }
469
470   /* Ignore failure, since the only way it can do so is in failing to
471      return to the original directory, and since we're about to exit,
472      that doesn't matter.  */
473   fts_close (fts);
474
475   return ok;
476 }