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