1/* setuidgid - run a command with the UID and GID of a specified user
2   Copyright (C) 2003-2010 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 Jim Meyering  */
18
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>
25
26#include "system.h"
27
28#include "error.h"
29#include "long-options.h"
30#include "mgetgroups.h"
31#include "quote.h"
32#include "xstrtol.h"
33
34#define PROGRAM_NAME "setuidgid"
35
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")
39
40#define SETUIDGID_FAILURE 111
41
42void
43usage (int status)
44{
45  if (status != EXIT_SUCCESS)
46    fprintf (stderr, _("Try `%s --help' for more information.\n"),
47             program_name);
48  else
49    {
50      printf (_("\
51Usage: %s [SHORT-OPTION]... USER COMMAND [ARGUMENT]...\n\
52  or:  %s LONG-OPTION\n\
53"),
54              program_name, program_name);
55
56      fputs (_("\
57Drop any supplemental groups, assume the user-ID and group-ID of the specified\n\
58USER (numeric ID or user name), and run COMMAND with any specified ARGUMENTs.\n\
59Exit with status 111 if unable to assume the required user and group ID.\n\
60Otherwise, exit with the exit status of COMMAND.\n\
61This program is useful only when run by root (user ID zero).\n\
62\n\
63"), stdout);
64      fputs (_("\
65  -g GID[,GID1...]  also set the primary group-ID to the numeric GID, and\n\
66                    (if specified) supplemental group IDs to GID1, ...\n\
67"), stdout);
68      fputs (HELP_OPTION_DESCRIPTION, stdout);
69      fputs (VERSION_OPTION_DESCRIPTION, stdout);
70      emit_ancillary_info ();
71    }
72  exit (status);
73}
74
75int
76main (int argc, char **argv)
77{
78  uid_t uid;
79  GETGROUPS_T *gids = NULL;
80  size_t n_gids = 0;
81  size_t n_gids_allocated = 0;
82  gid_t primary_gid;
83
84  initialize_main (&argc, &argv);
85  set_program_name (argv[0]);
86  setlocale (LC_ALL, "");
87  bindtextdomain (PACKAGE, LOCALEDIR);
88  textdomain (PACKAGE);
89
90  initialize_exit_failure (SETUIDGID_FAILURE);
91  atexit (close_stdout);
92
93  parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE_NAME, Version,
94                      usage, AUTHORS, (char const *) NULL);
95  {
96    int c;
97    while ((c = getopt_long (argc, argv, "+g:", NULL, NULL)) != -1)
98      {
99        switch (c)
100          {
101            case 'g':
102              {
103                unsigned long int tmp_ul;
104                char *gr = optarg;
105                char *ptr;
106                while (true)
107                  {
108                    if (! (xstrtoul (gr, &ptr, 10, &tmp_ul, NULL) == LONGINT_OK
109                           && tmp_ul <= GID_T_MAX))
110                      error (EXIT_FAILURE, 0, _("invalid group %s"),
111                             quote (gr));
112                    if (n_gids == n_gids_allocated)
113                      gids = X2NREALLOC (gids, &n_gids_allocated);
114                    gids[n_gids++] = tmp_ul;
115
116                    if (*ptr == '\0')
117                      break;
118                    if (*ptr != ',')
119                      {
120                        error (0, 0, _("invalid group %s"), quote (gr));
121                        usage (SETUIDGID_FAILURE);
122                      }
123                    gr = ptr + 1;
124                  }
125                break;
126              }
127
128            default:
129              usage (SETUIDGID_FAILURE);
130          }
131      }
132  }
133
134  if (argc <= optind + 1)
135    {
136      if (argc < optind + 1)
137        error (0, 0, _("missing operand"));
138      else
139        error (0, 0, _("missing operand after %s"), quote (argv[optind]));
140      usage (SETUIDGID_FAILURE);
141    }
142
143  {
144    const struct passwd *pwd;
145    unsigned long int tmp_ul;
146    char *user = argv[optind];
147    char *ptr;
148    bool have_uid = false;
149
150    if (xstrtoul (user, &ptr, 10, &tmp_ul, "") == LONGINT_OK
151        && tmp_ul <= UID_T_MAX)
152      {
153        uid = tmp_ul;
154        have_uid = true;
155      }
156
157    if (!have_uid)
158      {
159        pwd = getpwnam (user);
160        if (pwd == NULL)
161          {
162            error (SETUIDGID_FAILURE, errno,
163                   _("unknown user-ID: %s"), quote (user));
164            usage (SETUIDGID_FAILURE);
165          }
166        uid = pwd->pw_uid;
167      }
168    else if (n_gids == 0)
169      {
170        pwd = getpwuid (uid);
171        if (pwd == NULL)
172          {
173            error (SETUIDGID_FAILURE, errno,
174                   _("to use user-ID %s you need to use -g too"), quote (user));
175            usage (SETUIDGID_FAILURE);
176          }
177      }
178
179#if HAVE_SETGROUPS
180    if (n_gids == 0)
181      {
182        int n = xgetgroups (pwd->pw_name, pwd->pw_gid, &gids);
183        if (n <= 0)
184          error (EXIT_FAILURE, errno, _("failed to get groups for user %s"),
185                 quote (pwd->pw_name));
186        n_gids = n;
187      }
188
189    if (setgroups (n_gids, gids))
190      error (SETUIDGID_FAILURE, errno,
191             _("failed to set supplemental group(s)"));
192
193    primary_gid = gids[0];
194#else
195    primary_gid = pwd->pw_gid;
196#endif
197  }
198
199  if (setgid (primary_gid))
200    error (SETUIDGID_FAILURE, errno,
201           _("cannot set group-ID to %lu"), (unsigned long int) primary_gid);
202
203  if (setuid (uid))
204    error (SETUIDGID_FAILURE, errno,
205           _("cannot set user-ID to %lu"), (unsigned long int) uid);
206
207  {
208    char **cmd = argv + optind + 1;
209    int exit_status;
210    execvp (*cmd, cmd);
211    exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
212
213    error (0, errno, _("failed to run command %s"), quote (*cmd));
214    exit (exit_status);
215  }
216}
217