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