1 /* chown-core.c -- core functions for changing ownership.
2 Copyright (C) 2000, 2002, 2003, 2004, 2005, 2006 Free Software Foundation.
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)
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.
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. */
18 /* Extracted from chown.c/chgrp.c and librarified by Jim Meyering. */
22 #include <sys/types.h>
27 #include "chown-core.h"
32 #include "root-dev-ino.h"
37 /* we called fchown and close, and both succeeded */
40 /* required_uid and/or required_gid are specified, but don't match */
43 /* SAME_INODE check failed */
46 /* open/fchown isn't needed, isn't safe, or doesn't work due to
47 permissions problems; fall back on chown */
50 /* open, fstat, fchown, or close failed */
55 chopt_init (struct Chown_option *chopt)
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;
67 chopt_free (struct Chown_option *chopt ATTRIBUTE_UNUSED)
69 /* Deliberately do not free chopt->user_name or ->group_name.
70 They're not always allocated. */
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. */
78 gid_to_name (gid_t gid)
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));
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. */
92 uid_to_name (uid_t uid)
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));
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. */
106 describe_change (const char *file, enum Change_status changed,
107 char const *user, char const *group)
111 char *spec_allocated = NULL;
113 if (changed == CH_NOT_APPLIED)
115 printf (_("neither symbolic link %s nor referent has been changed\n"),
124 spec_allocated = xmalloc (strlen (user) + 1 + strlen (group) + 1);
125 stpcpy (stpcpy (stpcpy (spec_allocated, user), ":"), group);
126 spec = spec_allocated;
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"));
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"));
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"));
159 printf (fmt, quote (file), spec);
161 free (spec_allocated);
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.
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.
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.
180 Return one of the RCH_status values. */
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)
188 enum RCH_status status = RC_ok;
190 int open_flags = O_NONBLOCK | O_NOCTTY;
193 if (required_uid == (uid_t) -1 && required_gid == (gid_t) -1)
194 return RC_do_ordinary_chown;
196 if (! S_ISREG (orig_st->st_mode))
198 if (S_ISDIR (orig_st->st_mode))
199 open_flags |= O_DIRECTORY;
201 return RC_do_ordinary_chown;
204 fd = openat (cwd_fd, file, O_RDONLY | open_flags);
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);
210 if (fstat (fd, &st) != 0)
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))
217 if (fchown (fd, uid, gid) == 0)
219 status = (close (fd) == 0
229 { /* FIXME: remove these curly braces when we assume C99. */
230 int saved_errno = errno;
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. */
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)
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;
255 bool symlink_changed = true;
257 switch (ent->fts_info)
265 if (! chopt->recurse)
270 error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
275 error (0, ent->fts_errno, _("%s"), quote (file_full_name));
280 error (0, ent->fts_errno, _("cannot read directory %s"),
281 quote (file_full_name));
294 else if (required_uid == (uid_t) -1 && required_gid == (gid_t) -1
295 && chopt->verbosity == V_off && ! chopt->root_dev_ino)
298 file_stats = ent->fts_statp;
302 file_stats = ent->fts_statp;
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)
308 if (stat (file, &stat_buf) != 0)
310 error (0, errno, _("cannot dereference %s"),
311 quote (file_full_name));
315 file_stats = &stat_buf;
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));
325 if (do_chown && ROOT_DEV_INO_CHECK (chopt->root_dev_ino, file_stats))
327 ROOT_DEV_INO_WARN (file_full_name);
328 ok = do_chown = false;
333 if ( ! chopt->affect_symlink_referent)
335 ok = (lchownat (fts->fts_cwd_fd, file, uid, gid) == 0);
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)
343 symlink_changed = false;
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. */
359 = restricted_chown (fts->fts_cwd_fd, file, file_stats, uid, gid,
360 required_uid, required_gid);
366 case RC_do_ordinary_chown:
367 ok = (chownat (fts->fts_cwd_fd, file, uid, gid) == 0);
374 case RC_inode_changed:
375 /* FIXME: give a diagnostic in this case? */
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. */
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));
400 if (chopt->verbosity != V_off)
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)));
407 if (changed || chopt->verbosity == V_high)
409 enum Change_status ch_status =
411 : !symlink_changed ? CH_NOT_APPLIED
412 : !changed ? CH_NO_CHANGE_REQUESTED
414 describe_change (file_full_name, ch_status,
415 chopt->user_name, chopt->group_name);
419 if ( ! chopt->recurse)
420 fts_set (fts, ent, FTS_SKIP);
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. */
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)
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)
448 FTS *fts = xfts_open (files, bit_flags | stat_flags, NULL);
454 ent = fts_read (fts);
459 /* FIXME: try to give a better message */
460 error (0, errno, _("fts_read failed"));
466 ok &= change_file_owner (fts, ent, uid, gid,
467 required_uid, required_gid, chopt);
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. */