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