1c1a9769502eec874f209d05989ccc337c291616
[platform/upstream/coreutils.git] / src / chroot.c
1 /* chroot -- run command or shell with special root directory
2    Copyright (C) 1995-2013 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 /* Written by Roland McGrath.  */
18
19 #include <config.h>
20 #include <getopt.h>
21 #include <stdio.h>
22 #include <sys/types.h>
23 #include <grp.h>
24
25 #include "system.h"
26 #include "error.h"
27 #include "quote.h"
28 #include "userspec.h"
29 #include "xstrtol.h"
30
31 /* The official name of this program (e.g., no 'g' prefix).  */
32 #define PROGRAM_NAME "chroot"
33
34 #define AUTHORS proper_name ("Roland McGrath")
35
36 #ifndef MAXGID
37 # define MAXGID GID_T_MAX
38 #endif
39
40 enum
41 {
42   GROUPS = UCHAR_MAX + 1,
43   USERSPEC
44 };
45
46 static struct option const long_opts[] =
47 {
48   {"groups", required_argument, NULL, GROUPS},
49   {"userspec", required_argument, NULL, USERSPEC},
50   {GETOPT_HELP_OPTION_DECL},
51   {GETOPT_VERSION_OPTION_DECL},
52   {NULL, 0, NULL, 0}
53 };
54
55 #if ! HAVE_SETGROUPS
56 /* At least Interix lacks supplemental group support.  Define an
57    always-successful replacement to avoid checking for setgroups
58    availability everywhere, just to support broken platforms. */
59 static int
60 setgroups (size_t size ATTRIBUTE_UNUSED, gid_t const *list ATTRIBUTE_UNUSED)
61 {
62   return 0;
63 }
64 #endif
65
66 /* Call setgroups to set the supplementary groups to those listed in GROUPS.
67    GROUPS is a comma separated list of supplementary groups (names or numbers).
68    Parse that list, converting any names to numbers, and call setgroups on the
69    resulting numbers.  Upon any failure give a diagnostic and return nonzero.
70    Otherwise return zero.  */
71 static int
72 set_additional_groups (char const *groups)
73 {
74   GETGROUPS_T *gids = NULL;
75   size_t n_gids_allocated = 0;
76   size_t n_gids = 0;
77   char *buffer = xstrdup (groups);
78   char const *tmp;
79   int ret = 0;
80
81   for (tmp = strtok (buffer, ","); tmp; tmp = strtok (NULL, ","))
82     {
83       struct group *g;
84       unsigned long int value;
85
86       if (xstrtoul (tmp, NULL, 10, &value, "") == LONGINT_OK && value <= MAXGID)
87         g = getgrgid (value);
88       else
89         {
90           g = getgrnam (tmp);
91           if (g != NULL)
92             value = g->gr_gid;
93         }
94
95       if (g == NULL)
96         {
97           error (0, errno, _("invalid group %s"), quote (tmp));
98           ret = -1;
99           continue;
100         }
101
102       if (n_gids == n_gids_allocated)
103         gids = X2NREALLOC (gids, &n_gids_allocated);
104       gids[n_gids++] = value;
105     }
106
107   if (ret == 0 && n_gids == 0)
108     {
109       error (0, 0, _("invalid group list %s"), quote (groups));
110       ret = -1;
111     }
112
113   if (ret == 0)
114     {
115       ret = setgroups (n_gids, gids);
116       if (ret)
117         error (0, errno, _("failed to set additional groups"));
118     }
119
120   free (buffer);
121   free (gids);
122   return ret;
123 }
124
125 void
126 usage (int status)
127 {
128   if (status != EXIT_SUCCESS)
129     emit_try_help ();
130   else
131     {
132       printf (_("\
133 Usage: %s [OPTION] NEWROOT [COMMAND [ARG]...]\n\
134   or:  %s OPTION\n\
135 "), program_name, program_name);
136
137       fputs (_("\
138 Run COMMAND with root directory set to NEWROOT.\n\
139 \n\
140 "), stdout);
141
142       fputs (_("\
143   --userspec=USER:GROUP  specify user and group (ID or name) to use\n\
144   --groups=G_LIST        specify supplementary groups as g1,g2,..,gN\n\
145 "), stdout);
146
147       fputs (HELP_OPTION_DESCRIPTION, stdout);
148       fputs (VERSION_OPTION_DESCRIPTION, stdout);
149       fputs (_("\
150 \n\
151 If no command is given, run '${SHELL} -i' (default: '/bin/sh -i').\n\
152 "), stdout);
153       emit_ancillary_info ();
154     }
155   exit (status);
156 }
157
158 int
159 main (int argc, char **argv)
160 {
161   int c;
162   char const *userspec = NULL;
163   char const *groups = NULL;
164
165   initialize_main (&argc, &argv);
166   set_program_name (argv[0]);
167   setlocale (LC_ALL, "");
168   bindtextdomain (PACKAGE, LOCALEDIR);
169   textdomain (PACKAGE);
170
171   initialize_exit_failure (EXIT_CANCELED);
172   atexit (close_stdout);
173
174   while ((c = getopt_long (argc, argv, "+", long_opts, NULL)) != -1)
175     {
176       switch (c)
177         {
178         case USERSPEC:
179           userspec = optarg;
180           break;
181
182         case GROUPS:
183           groups = optarg;
184           break;
185
186         case_GETOPT_HELP_CHAR;
187
188         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
189
190         default:
191           usage (EXIT_CANCELED);
192         }
193     }
194
195   if (argc <= optind)
196     {
197       error (0, 0, _("missing operand"));
198       usage (EXIT_CANCELED);
199     }
200
201   if (chroot (argv[optind]) != 0)
202     error (EXIT_CANCELED, errno, _("cannot change root directory to %s"),
203            argv[optind]);
204
205   if (chdir ("/"))
206     error (EXIT_CANCELED, errno, _("cannot chdir to root directory"));
207
208   if (argc == optind + 1)
209     {
210       /* No command.  Run an interactive shell.  */
211       char *shell = getenv ("SHELL");
212       if (shell == NULL)
213         shell = bad_cast ("/bin/sh");
214       argv[0] = shell;
215       argv[1] = bad_cast ("-i");
216       argv[2] = NULL;
217     }
218   else
219     {
220       /* The following arguments give the command.  */
221       argv += optind + 1;
222     }
223
224   bool fail = false;
225
226   /* Attempt to set all three: supplementary groups, group ID, user ID.
227      Diagnose any failures.  If any have failed, exit before execvp.  */
228   if (userspec)
229     {
230       uid_t uid = -1;
231       gid_t gid = -1;
232       char *user;
233       char *group;
234       char const *err = parse_user_spec (userspec, &uid, &gid, &user, &group);
235
236       if (err)
237         error (EXIT_CANCELED, errno, "%s", err);
238
239       free (user);
240       free (group);
241
242       if (groups && set_additional_groups (groups))
243         fail = true;
244
245       if (gid != (gid_t) -1 && setgid (gid))
246         {
247           error (0, errno, _("failed to set group-ID"));
248           fail = true;
249         }
250
251       if (uid != (uid_t) -1 && setuid (uid))
252         {
253           error (0, errno, _("failed to set user-ID"));
254           fail = true;
255         }
256     }
257   else
258     {
259       /* Yes, this call is identical to the one above.
260          However, when --userspec and --groups groups are used together,
261          we don't want to call this function until after parsing USER:GROUP,
262          and it must be called before setuid.  */
263       if (groups && set_additional_groups (groups))
264         fail = true;
265     }
266
267   if (fail)
268     exit (EXIT_CANCELED);
269
270   /* Execute the given command.  */
271   execvp (argv[0], argv);
272
273   {
274     int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
275     error (0, errno, _("failed to run command %s"), quote (argv[0]));
276     exit (exit_status);
277   }
278 }