source: trunk/coreutils/src/chown-core.c@ 2555

Last change on this file since 2555 was 2554, checked in by bird, 20 years ago

coretuils-5.94

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