copy: factor sparse-copying code into its own function, because
[coreutils.git] / src / setuidgid.c
blob4cbb1f2de1390909d5e20e0ecae108473e854136
1 /* setuidgid - run a command with the UID and GID of a specified user
2 Copyright (C) 2003-2011 Free Software Foundation, Inc.
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.
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, see <http://www.gnu.org/licenses/>. */
17 /* Written by Jim Meyering */
19 #include <config.h>
20 #include <getopt.h>
21 #include <stdio.h>
22 #include <sys/types.h>
23 #include <pwd.h>
24 #include <grp.h>
26 #include "system.h"
28 #include "error.h"
29 #include "long-options.h"
30 #include "mgetgroups.h"
31 #include "quote.h"
32 #include "xstrtol.h"
34 #define PROGRAM_NAME "setuidgid"
36 /* I wrote this program from scratch, based on the description of
37 D.J. Bernstein's program: http://cr.yp.to/daemontools/setuidgid.html. */
38 #define AUTHORS proper_name ("Jim Meyering")
40 #define SETUIDGID_FAILURE 111
42 void
43 usage (int status)
45 if (status != EXIT_SUCCESS)
46 fprintf (stderr, _("Try `%s --help' for more information.\n"),
47 program_name);
48 else
50 printf (_("\
51 Usage: %s [SHORT-OPTION]... USER COMMAND [ARGUMENT]...\n\
52 or: %s LONG-OPTION\n\
53 "),
54 program_name, program_name);
56 fputs (_("\
57 Drop any supplemental groups, assume the user-ID and group-ID of the specified\
58 \n\
59 USER (numeric ID or user name), and run COMMAND with any specified ARGUMENTs.\n\
60 Exit with status 111 if unable to assume the required user and group ID.\n\
61 Otherwise, exit with the exit status of COMMAND.\n\
62 This program is useful only when run by root (user ID zero).\n\
63 \n\
64 "), stdout);
65 fputs (_("\
66 -g GID[,GID1...] also set the primary group-ID to the numeric GID, and\n\
67 (if specified) supplemental group IDs to GID1, ...\n\
68 "), stdout);
69 fputs (HELP_OPTION_DESCRIPTION, stdout);
70 fputs (VERSION_OPTION_DESCRIPTION, stdout);
71 emit_ancillary_info ();
73 exit (status);
76 int
77 main (int argc, char **argv)
79 uid_t uid;
80 GETGROUPS_T *gids = NULL;
81 size_t n_gids = 0;
82 size_t n_gids_allocated = 0;
83 gid_t primary_gid;
85 initialize_main (&argc, &argv);
86 set_program_name (argv[0]);
87 setlocale (LC_ALL, "");
88 bindtextdomain (PACKAGE, LOCALEDIR);
89 textdomain (PACKAGE);
91 initialize_exit_failure (SETUIDGID_FAILURE);
92 atexit (close_stdout);
94 parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE_NAME, Version,
95 usage, AUTHORS, (char const *) NULL);
97 int c;
98 while ((c = getopt_long (argc, argv, "+g:", NULL, NULL)) != -1)
100 switch (c)
102 case 'g':
104 unsigned long int tmp_ul;
105 char *gr = optarg;
106 char *ptr;
107 while (true)
109 if (! (xstrtoul (gr, &ptr, 10, &tmp_ul, NULL) == LONGINT_OK
110 && tmp_ul <= GID_T_MAX))
111 error (EXIT_FAILURE, 0, _("invalid group %s"),
112 quote (gr));
113 if (n_gids == n_gids_allocated)
114 gids = X2NREALLOC (gids, &n_gids_allocated);
115 gids[n_gids++] = tmp_ul;
117 if (*ptr == '\0')
118 break;
119 if (*ptr != ',')
121 error (0, 0, _("invalid group %s"), quote (gr));
122 usage (SETUIDGID_FAILURE);
124 gr = ptr + 1;
126 break;
129 default:
130 usage (SETUIDGID_FAILURE);
135 if (argc <= optind + 1)
137 if (argc < optind + 1)
138 error (0, 0, _("missing operand"));
139 else
140 error (0, 0, _("missing operand after %s"), quote (argv[optind]));
141 usage (SETUIDGID_FAILURE);
145 const struct passwd *pwd;
146 unsigned long int tmp_ul;
147 char *user = argv[optind];
148 char *ptr;
149 bool have_uid = false;
151 if (xstrtoul (user, &ptr, 10, &tmp_ul, "") == LONGINT_OK
152 && tmp_ul <= UID_T_MAX)
154 uid = tmp_ul;
155 have_uid = true;
158 if (!have_uid)
160 pwd = getpwnam (user);
161 if (pwd == NULL)
163 error (SETUIDGID_FAILURE, errno,
164 _("unknown user-ID: %s"), quote (user));
165 usage (SETUIDGID_FAILURE);
167 uid = pwd->pw_uid;
169 else if (n_gids == 0)
171 pwd = getpwuid (uid);
172 if (pwd == NULL)
174 error (SETUIDGID_FAILURE, errno,
175 _("to use user-ID %s you need to use -g too"), quote (user));
176 usage (SETUIDGID_FAILURE);
180 #if HAVE_SETGROUPS
181 if (n_gids == 0)
183 int n = xgetgroups (pwd->pw_name, pwd->pw_gid, &gids);
184 if (n <= 0)
185 error (EXIT_FAILURE, errno, _("failed to get groups for user %s"),
186 quote (pwd->pw_name));
187 n_gids = n;
190 if (setgroups (n_gids, gids))
191 error (SETUIDGID_FAILURE, errno,
192 _("failed to set supplemental group(s)"));
194 primary_gid = gids[0];
195 #else
196 primary_gid = pwd->pw_gid;
197 #endif
200 if (setgid (primary_gid))
201 error (SETUIDGID_FAILURE, errno,
202 _("cannot set group-ID to %lu"), (unsigned long int) primary_gid);
204 if (setuid (uid))
205 error (SETUIDGID_FAILURE, errno,
206 _("cannot set user-ID to %lu"), (unsigned long int) uid);
209 char **cmd = argv + optind + 1;
210 int exit_status;
211 execvp (*cmd, cmd);
212 exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
214 error (0, errno, _("failed to run command %s"), quote (*cmd));
215 exit (exit_status);