summaryrefslogtreecommitdiff
path: root/src/chown-core.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/chown-core.c')
-rw-r--r--src/chown-core.c469
1 files changed, 255 insertions, 214 deletions
diff --git a/src/chown-core.c b/src/chown-core.c
index bd987a8..b699993 100644
--- a/src/chown-core.c
+++ b/src/chown-core.c
@@ -1,10 +1,10 @@
/* chown-core.c -- core functions for changing ownership.
- Copyright (C) 2000, 2002, 2003, 2004, 2005, 2006, 2007 Free Software Foundation.
+ Copyright (C) 2000-2016 Free Software Foundation, Inc.
- This program is free software; you can redistribute it and/or modify
+ This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2, or (at your option)
- any later version.
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -12,8 +12,7 @@
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software Foundation,
- Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* Extracted from chown.c/chgrp.c and librarified by Jim Meyering. */
@@ -26,9 +25,7 @@
#include "system.h"
#include "chown-core.h"
#include "error.h"
-#include "inttostr.h"
-#include "openat.h"
-#include "quote.h"
+#include "ignore-value.h"
#include "root-dev-ino.h"
#include "xfts.h"
@@ -70,7 +67,7 @@ chopt_init (struct Chown_option *chopt)
}
extern void
-chopt_free (struct Chown_option *chopt ATTRIBUTE_UNUSED)
+chopt_free (struct Chown_option *chopt _GL_UNUSED)
{
/* Deliberately do not free chopt->user_name or ->group_name.
They're not always allocated. */
@@ -86,8 +83,8 @@ gid_to_name (gid_t gid)
char buf[INT_BUFSIZE_BOUND (intmax_t)];
struct group *grp = getgrgid (gid);
return xstrdup (grp ? grp->gr_name
- : TYPE_SIGNED (gid_t) ? imaxtostr (gid, buf)
- : umaxtostr (gid, buf));
+ : TYPE_SIGNED (gid_t) ? imaxtostr (gid, buf)
+ : umaxtostr (gid, buf));
}
/* Convert the numeric user-id, UID, to a string stored in xmalloc'd memory,
@@ -100,8 +97,35 @@ uid_to_name (uid_t uid)
char buf[INT_BUFSIZE_BOUND (intmax_t)];
struct passwd *pwd = getpwuid (uid);
return xstrdup (pwd ? pwd->pw_name
- : TYPE_SIGNED (uid_t) ? imaxtostr (uid, buf)
- : umaxtostr (uid, buf));
+ : TYPE_SIGNED (uid_t) ? imaxtostr (uid, buf)
+ : umaxtostr (uid, buf));
+}
+
+/* Allocate a string representing USER and GROUP. */
+
+static char *
+user_group_str (char const *user, char const *group)
+{
+ char *spec = NULL;
+
+ if (user)
+ {
+ if (group)
+ {
+ spec = xmalloc (strlen (user) + 1 + strlen (group) + 1);
+ stpcpy (stpcpy (stpcpy (spec, user), ":"), group);
+ }
+ else
+ {
+ spec = xstrdup (user);
+ }
+ }
+ else if (group)
+ {
+ spec = xstrdup (group);
+ }
+
+ return spec;
}
/* Tell the user how/if the user and group of FILE have been changed.
@@ -110,68 +134,67 @@ uid_to_name (uid_t uid)
static void
describe_change (const char *file, enum Change_status changed,
- char const *user, char const *group)
+ char const *old_user, char const *old_group,
+ char const *user, char const *group)
{
const char *fmt;
- char const *spec;
- char *spec_allocated = NULL;
+ char *old_spec;
+ char *spec;
if (changed == CH_NOT_APPLIED)
{
printf (_("neither symbolic link %s nor referent has been changed\n"),
- quote (file));
+ quoteaf (file));
return;
}
- if (user)
- {
- if (group)
- {
- spec_allocated = xmalloc (strlen (user) + 1 + strlen (group) + 1);
- stpcpy (stpcpy (stpcpy (spec_allocated, user), ":"), group);
- spec = spec_allocated;
- }
- else
- {
- spec = user;
- }
- }
- else
- {
- spec = group;
- }
+ spec = user_group_str (user, group);
+ old_spec = user_group_str (user ? old_user : NULL, group ? old_group : NULL);
switch (changed)
{
case CH_SUCCEEDED:
- fmt = (user ? _("changed ownership of %s to %s\n")
- : group ? _("changed group of %s to %s\n")
- : _("no change to ownership of %s\n"));
+ fmt = (user ? _("changed ownership of %s from %s to %s\n")
+ : group ? _("changed group of %s from %s to %s\n")
+ : _("no change to ownership of %s\n"));
break;
case CH_FAILED:
- fmt = (user ? _("failed to change ownership of %s to %s\n")
- : group ? _("failed to change group of %s to %s\n")
- : _("failed to change ownership of %s\n"));
+ if (old_spec)
+ {
+ fmt = (user ? _("failed to change ownership of %s from %s to %s\n")
+ : group ? _("failed to change group of %s from %s to %s\n")
+ : _("failed to change ownership of %s\n"));
+ }
+ else
+ {
+ fmt = (user ? _("failed to change ownership of %s to %s\n")
+ : group ? _("failed to change group of %s to %s\n")
+ : _("failed to change ownership of %s\n"));
+ free (old_spec);
+ old_spec = spec;
+ spec = NULL;
+ }
break;
case CH_NO_CHANGE_REQUESTED:
fmt = (user ? _("ownership of %s retained as %s\n")
- : group ? _("group of %s retained as %s\n")
- : _("ownership of %s retained\n"));
+ : group ? _("group of %s retained as %s\n")
+ : _("ownership of %s retained\n"));
break;
default:
abort ();
}
- printf (fmt, quote (file), spec);
+ printf (fmt, quoteaf (file), old_spec, spec);
- free (spec_allocated);
+ free (old_spec);
+ free (spec);
}
/* Change the owner and/or group of the FILE to UID and/or GID (safely)
only if REQUIRED_UID and REQUIRED_GID match the owner and group IDs
- of FILE. ORIG_ST must be the result of `stat'ing FILE.
+ of FILE. ORIG_ST must be the result of 'stat'ing FILE.
- The `safely' part above means that we can't simply use chown(2),
+ The 'safely' part above means that we can't simply use chown(2),
since FILE might be replaced with some other file between the time
of the preceding stat/lstat and this chown call. So here we open
FILE and do everything else via the resulting file descriptor.
@@ -187,9 +210,9 @@ describe_change (const char *file, enum Change_status changed,
static enum RCH_status
restricted_chown (int cwd_fd, char const *file,
- struct stat const *orig_st,
- uid_t uid, gid_t gid,
- uid_t required_uid, gid_t required_gid)
+ struct stat const *orig_st,
+ uid_t uid, gid_t gid,
+ uid_t required_uid, gid_t required_gid)
{
enum RCH_status status = RC_ok;
struct stat st;
@@ -202,15 +225,15 @@ restricted_chown (int cwd_fd, char const *file,
if (! S_ISREG (orig_st->st_mode))
{
if (S_ISDIR (orig_st->st_mode))
- open_flags |= O_DIRECTORY;
+ open_flags |= O_DIRECTORY;
else
- return RC_do_ordinary_chown;
+ return RC_do_ordinary_chown;
}
fd = openat (cwd_fd, file, O_RDONLY | open_flags);
if (! (0 <= fd
- || (errno == EACCES && S_ISREG (orig_st->st_mode)
- && 0 <= (fd = openat (cwd_fd, file, O_WRONLY | open_flags)))))
+ || (errno == EACCES && S_ISREG (orig_st->st_mode)
+ && 0 <= (fd = openat (cwd_fd, file, O_WRONLY | open_flags)))))
return (errno == EACCES ? RC_do_ordinary_chown : RC_error);
if (fstat (fd, &st) != 0)
@@ -218,26 +241,24 @@ restricted_chown (int cwd_fd, char const *file,
else if (! SAME_INODE (*orig_st, st))
status = RC_inode_changed;
else if ((required_uid == (uid_t) -1 || required_uid == st.st_uid)
- && (required_gid == (gid_t) -1 || required_gid == st.st_gid))
+ && (required_gid == (gid_t) -1 || required_gid == st.st_gid))
{
if (fchown (fd, uid, gid) == 0)
- {
- status = (close (fd) == 0
- ? RC_ok : RC_error);
- return status;
- }
+ {
+ status = (close (fd) == 0
+ ? RC_ok : RC_error);
+ return status;
+ }
else
- {
- status = RC_error;
- }
+ {
+ status = RC_error;
+ }
}
- { /* FIXME: remove these curly braces when we assume C99. */
- int saved_errno = errno;
- close (fd);
- errno = saved_errno;
- return status;
- }
+ int saved_errno = errno;
+ close (fd);
+ errno = saved_errno;
+ return status;
}
/* Change the owner and/or group of the file specified by FTS and ENT
@@ -248,9 +269,9 @@ restricted_chown (int cwd_fd, char const *file,
Return true if successful. */
static bool
change_file_owner (FTS *fts, FTSENT *ent,
- uid_t uid, gid_t gid,
- uid_t required_uid, gid_t required_gid,
- struct Chown_option const *chopt)
+ uid_t uid, gid_t gid,
+ uid_t required_uid, gid_t required_gid,
+ struct Chown_option const *chopt)
{
char const *file_full_name = ent->fts_path;
char const *file = ent->fts_accpath;
@@ -264,56 +285,68 @@ change_file_owner (FTS *fts, FTSENT *ent,
{
case FTS_D:
if (chopt->recurse)
- {
- if (ROOT_DEV_INO_CHECK (chopt->root_dev_ino, ent->fts_statp))
- {
- /* This happens e.g., with "chown -R --preserve-root 0 /"
- and with "chown -RH --preserve-root 0 symlink-to-root". */
- ROOT_DEV_INO_WARN (file_full_name);
- /* Tell fts not to traverse into this hierarchy. */
- fts_set (fts, ent, FTS_SKIP);
- /* Ensure that we do not process "/" on the second visit. */
- ent = fts_read (fts);
- return false;
- }
- return true;
- }
+ {
+ if (ROOT_DEV_INO_CHECK (chopt->root_dev_ino, ent->fts_statp))
+ {
+ /* This happens e.g., with "chown -R --preserve-root 0 /"
+ and with "chown -RH --preserve-root 0 symlink-to-root". */
+ ROOT_DEV_INO_WARN (file_full_name);
+ /* Tell fts not to traverse into this hierarchy. */
+ fts_set (fts, ent, FTS_SKIP);
+ /* Ensure that we do not process "/" on the second visit. */
+ ignore_value (fts_read (fts));
+ return false;
+ }
+ return true;
+ }
break;
case FTS_DP:
if (! chopt->recurse)
- return true;
+ return true;
break;
case FTS_NS:
/* For a top-level file or directory, this FTS_NS (stat failed)
- indicator is determined at the time of the initial fts_open call.
- With programs like chmod, chown, and chgrp, that modify
- permissions, it is possible that the file in question is
- accessible when control reaches this point. So, if this is
- the first time we've seen the FTS_NS for this file, tell
- fts_read to stat it "again". */
+ indicator is determined at the time of the initial fts_open call.
+ With programs like chmod, chown, and chgrp, that modify
+ permissions, it is possible that the file in question is
+ accessible when control reaches this point. So, if this is
+ the first time we've seen the FTS_NS for this file, tell
+ fts_read to stat it "again". */
if (ent->fts_level == 0 && ent->fts_number == 0)
- {
- ent->fts_number = 1;
- fts_set (fts, ent, FTS_AGAIN);
- return true;
- }
- error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
+ {
+ ent->fts_number = 1;
+ fts_set (fts, ent, FTS_AGAIN);
+ return true;
+ }
+ if (! chopt->force_silent)
+ error (0, ent->fts_errno, _("cannot access %s"),
+ quoteaf (file_full_name));
ok = false;
break;
case FTS_ERR:
- error (0, ent->fts_errno, _("%s"), quote (file_full_name));
+ if (! chopt->force_silent)
+ error (0, ent->fts_errno, "%s", quotef (file_full_name));
ok = false;
break;
case FTS_DNR:
- error (0, ent->fts_errno, _("cannot read directory %s"),
- quote (file_full_name));
+ if (! chopt->force_silent)
+ error (0, ent->fts_errno, _("cannot read directory %s"),
+ quoteaf (file_full_name));
ok = false;
break;
+ case FTS_DC: /* directory that causes cycles */
+ if (cycle_warning_required (fts, ent))
+ {
+ emit_cycle_warning (file_full_name);
+ return false;
+ }
+ break;
+
default:
break;
}
@@ -324,9 +357,9 @@ change_file_owner (FTS *fts, FTSENT *ent,
file_stats = NULL;
}
else if (required_uid == (uid_t) -1 && required_gid == (gid_t) -1
- && chopt->verbosity == V_off
- && ! chopt->root_dev_ino
- && ! chopt->affect_symlink_referent)
+ && chopt->verbosity == V_off
+ && ! chopt->root_dev_ino
+ && ! chopt->affect_symlink_referent)
{
do_chown = true;
file_stats = ent->fts_statp;
@@ -336,24 +369,25 @@ change_file_owner (FTS *fts, FTSENT *ent,
file_stats = ent->fts_statp;
/* If this is a symlink and we're dereferencing them,
- stat it to get info on the referent. */
+ stat it to get info on the referent. */
if (chopt->affect_symlink_referent && S_ISLNK (file_stats->st_mode))
- {
- if (fstatat (fts->fts_cwd_fd, file, &stat_buf, 0) != 0)
- {
- error (0, errno, _("cannot dereference %s"),
- quote (file_full_name));
- ok = false;
- }
-
- file_stats = &stat_buf;
- }
+ {
+ if (fstatat (fts->fts_cwd_fd, file, &stat_buf, 0) != 0)
+ {
+ if (! chopt->force_silent)
+ error (0, errno, _("cannot dereference %s"),
+ quoteaf (file_full_name));
+ ok = false;
+ }
+
+ file_stats = &stat_buf;
+ }
do_chown = (ok
- && (required_uid == (uid_t) -1
- || required_uid == file_stats->st_uid)
- && (required_gid == (gid_t) -1
- || required_gid == file_stats->st_gid));
+ && (required_uid == (uid_t) -1
+ || required_uid == file_stats->st_uid)
+ && (required_gid == (gid_t) -1
+ || required_gid == file_stats->st_gid));
}
/* This happens when chown -LR --preserve-root encounters a symlink-to-/. */
@@ -368,89 +402,94 @@ change_file_owner (FTS *fts, FTSENT *ent,
if (do_chown)
{
if ( ! chopt->affect_symlink_referent)
- {
- ok = (lchownat (fts->fts_cwd_fd, file, uid, gid) == 0);
-
- /* Ignore any error due to lack of support; POSIX requires
- this behavior for top-level symbolic links with -h, and
- implies that it's required for all symbolic links. */
- if (!ok && errno == EOPNOTSUPP)
- {
- ok = true;
- symlink_changed = false;
- }
- }
+ {
+ ok = (lchownat (fts->fts_cwd_fd, file, uid, gid) == 0);
+
+ /* Ignore any error due to lack of support; POSIX requires
+ this behavior for top-level symbolic links with -h, and
+ implies that it's required for all symbolic links. */
+ if (!ok && errno == EOPNOTSUPP)
+ {
+ ok = true;
+ symlink_changed = false;
+ }
+ }
else
- {
- /* If possible, avoid a race condition with --from=O:G and without the
- (-h) --no-dereference option. If fts's stat call determined
- that the uid/gid of FILE matched the --from=O:G-selected
- owner and group IDs, blindly using chown(2) here could lead
- chown(1) or chgrp(1) mistakenly to dereference a *symlink*
- to an arbitrary file that an attacker had moved into the
- place of FILE during the window between the stat and
- chown(2) calls. If FILE is a regular file or a directory
- that can be opened, this race condition can be avoided safely. */
-
- enum RCH_status err
- = restricted_chown (fts->fts_cwd_fd, file, file_stats, uid, gid,
- required_uid, required_gid);
- switch (err)
- {
- case RC_ok:
- break;
-
- case RC_do_ordinary_chown:
- ok = (chownat (fts->fts_cwd_fd, file, uid, gid) == 0);
- break;
-
- case RC_error:
- ok = false;
- break;
-
- case RC_inode_changed:
- /* FIXME: give a diagnostic in this case? */
- case RC_excluded:
- do_chown = false;
- ok = false;
- break;
-
- default:
- abort ();
- }
- }
-
- /* On some systems (e.g., Linux-2.4.x),
- the chown function resets the `special' permission bits.
- Do *not* restore those bits; doing so would open a window in
- which a malicious user, M, could subvert a chown command run
- by some other user and operating on files in a directory
- where M has write access. */
+ {
+ /* If possible, avoid a race condition with --from=O:G and without the
+ (-h) --no-dereference option. If fts's stat call determined
+ that the uid/gid of FILE matched the --from=O:G-selected
+ owner and group IDs, blindly using chown(2) here could lead
+ chown(1) or chgrp(1) mistakenly to dereference a *symlink*
+ to an arbitrary file that an attacker had moved into the
+ place of FILE during the window between the stat and
+ chown(2) calls. If FILE is a regular file or a directory
+ that can be opened, this race condition can be avoided safely. */
+
+ enum RCH_status err
+ = restricted_chown (fts->fts_cwd_fd, file, file_stats, uid, gid,
+ required_uid, required_gid);
+ switch (err)
+ {
+ case RC_ok:
+ break;
+
+ case RC_do_ordinary_chown:
+ ok = (chownat (fts->fts_cwd_fd, file, uid, gid) == 0);
+ break;
+
+ case RC_error:
+ ok = false;
+ break;
+
+ case RC_inode_changed:
+ /* FIXME: give a diagnostic in this case? */
+ case RC_excluded:
+ do_chown = false;
+ ok = false;
+ break;
+
+ default:
+ abort ();
+ }
+ }
+
+ /* On some systems (e.g., GNU/Linux 2.4.x),
+ the chown function resets the 'special' permission bits.
+ Do *not* restore those bits; doing so would open a window in
+ which a malicious user, M, could subvert a chown command run
+ by some other user and operating on files in a directory
+ where M has write access. */
if (do_chown && !ok && ! chopt->force_silent)
- error (0, errno, (uid != (uid_t) -1
- ? _("changing ownership of %s")
- : _("changing group of %s")),
- quote (file_full_name));
+ error (0, errno, (uid != (uid_t) -1
+ ? _("changing ownership of %s")
+ : _("changing group of %s")),
+ quoteaf (file_full_name));
}
if (chopt->verbosity != V_off)
{
bool changed =
- ((do_chown & ok & symlink_changed)
- && ! ((uid == (uid_t) -1 || uid == file_stats->st_uid)
- && (gid == (gid_t) -1 || gid == file_stats->st_gid)));
+ ((do_chown && ok && symlink_changed)
+ && ! ((uid == (uid_t) -1 || uid == file_stats->st_uid)
+ && (gid == (gid_t) -1 || gid == file_stats->st_gid)));
if (changed || chopt->verbosity == V_high)
- {
- enum Change_status ch_status =
- (!ok ? CH_FAILED
- : !symlink_changed ? CH_NOT_APPLIED
- : !changed ? CH_NO_CHANGE_REQUESTED
- : CH_SUCCEEDED);
- describe_change (file_full_name, ch_status,
- chopt->user_name, chopt->group_name);
- }
+ {
+ enum Change_status ch_status =
+ (!ok ? CH_FAILED
+ : !symlink_changed ? CH_NOT_APPLIED
+ : !changed ? CH_NO_CHANGE_REQUESTED
+ : CH_SUCCEEDED);
+ char *old_usr = file_stats ? uid_to_name (file_stats->st_uid) : NULL;
+ char *old_grp = file_stats ? gid_to_name (file_stats->st_gid) : NULL;
+ describe_change (file_full_name, ch_status,
+ old_usr, old_grp,
+ chopt->user_name, chopt->group_name);
+ free (old_usr);
+ free (old_grp);
+ }
}
if ( ! chopt->recurse)
@@ -470,18 +509,18 @@ change_file_owner (FTS *fts, FTSENT *ent,
Return true if successful. */
extern bool
chown_files (char **files, int bit_flags,
- uid_t uid, gid_t gid,
- uid_t required_uid, gid_t required_gid,
- struct Chown_option const *chopt)
+ uid_t uid, gid_t gid,
+ uid_t required_uid, gid_t required_gid,
+ struct Chown_option const *chopt)
{
bool ok = true;
/* Use lstat and stat only if they're needed. */
int stat_flags = ((required_uid != (uid_t) -1 || required_gid != (gid_t) -1
- || chopt->affect_symlink_referent
- || chopt->verbosity != V_off)
- ? 0
- : FTS_NOSTAT);
+ || chopt->affect_symlink_referent
+ || chopt->verbosity != V_off)
+ ? 0
+ : FTS_NOSTAT);
FTS *fts = xfts_open (files, bit_flags | stat_flags, NULL);
@@ -491,24 +530,26 @@ chown_files (char **files, int bit_flags,
ent = fts_read (fts);
if (ent == NULL)
- {
- if (errno != 0)
- {
- /* FIXME: try to give a better message */
- error (0, errno, _("fts_read failed"));
- ok = false;
- }
- break;
- }
+ {
+ if (errno != 0)
+ {
+ /* FIXME: try to give a better message */
+ if (! chopt->force_silent)
+ error (0, errno, _("fts_read failed"));
+ ok = false;
+ }
+ break;
+ }
ok &= change_file_owner (fts, ent, uid, gid,
- required_uid, required_gid, chopt);
+ required_uid, required_gid, chopt);
}
- /* Ignore failure, since the only way it can do so is in failing to
- return to the original directory, and since we're about to exit,
- that doesn't matter. */
- fts_close (fts);
+ if (fts_close (fts) != 0)
+ {
+ error (0, errno, _("fts_close failed"));
+ ok = false;
+ }
return ok;
}