Add missing includes for setpriority()
[untie.git] / untie.c
blob29d8aac95f1b97e9741f80c47e4691ff61ac366e
1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16 * See the COPYING file for license information.
18 * Copyright (c) 2006, 2007 Guillaume Chazarain <guichaz@yahoo.fr>
21 #include <sys/time.h>
22 #include <sys/resource.h>
23 #include <sys/wait.h>
24 #include <errno.h>
25 #include <sched.h>
26 #include <signal.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <string.h>
32 #include <grp.h>
33 #include <pwd.h>
36 * Copied from linux-2.6/include/linux/sched.h
37 * Compile on old libc, run on new kernel :-)
39 #define CLONE_NEWNS 0x00020000 /* New namespace */
40 #define CLONE_NEWUTS 0x04000000 /* New utsname */
41 #define CLONE_NEWIPC 0x08000000 /* New ipcs */
43 #include "cmdline.h"
46 * We find out the UID/GID infos before the chroot as we may need /etc/passwd.
47 * The results are stored in this struct.
49 struct user_info {
50 uid_t uid;
51 gid_t gid;
52 size_t nr_groups;
53 gid_t *groups;
57 * The temporary stack for the cloned process
59 #define STACK_SIZE (1 << 20)
60 static char stack[STACK_SIZE];
63 * Get the clone(2) flags from the command line arguments
65 static int get_flags(struct gengetopt_args_info *args_info)
67 int flags = 0;
68 unsigned int i;
70 if (args_info->mount_flag)
71 flags |= CLONE_NEWNS;
73 if (args_info->uname_flag)
74 flags |= CLONE_NEWUTS;
76 if (args_info->ipc_flag)
77 flags |= CLONE_NEWIPC;
79 for (i = 0; i < args_info->mask_given; i++)
80 flags |= args_info->mask_arg[i];
82 return flags;
86 * Wrapper for malloc, exit instead of returning NULL
88 static void *xmalloc(int size)
90 void *res = malloc(size);
91 if (!res) {
92 perror("malloc");
93 exit(EXIT_FAILURE);
95 return res;
99 * Do a chroot() if requested. Do also a chdir() to prevent the simplest
100 * form a chroot escape.
102 static void perform_chroot(struct gengetopt_args_info *ggo)
104 if (ggo->chroot_given) {
105 if (chroot(ggo->chroot_arg) < 0) {
106 perror("chroot");
107 exit(EXIT_FAILURE);
110 if (chdir("/") < 0) {
111 perror("chdir(\"/\")");
112 exit(EXIT_FAILURE);
118 * Find the UID associated to a username
120 static uid_t name_to_uid(const char *name)
122 struct passwd *pass;
124 errno = 0;
125 pass = getpwnam(name);
127 if (!pass) {
128 if (errno)
129 perror("getpwnam");
130 else
131 fprintf(stderr, "%s: user not found\n", name);
132 exit(EXIT_FAILURE);
135 return pass->pw_uid;
139 * Find the GID associated to a group
141 static gid_t name_to_gid(const char *name)
143 struct group *grp;
145 errno = 0;
146 grp = getgrnam(name);
148 if (!grp) {
149 if (errno)
150 perror("getgrnam");
151 else
152 fprintf(stderr, "%s: group not found\n", name);
153 exit(EXIT_FAILURE);
156 return grp->gr_gid;
160 * Transform the command line args (ggo) into our struct (user)
162 static void get_user_infos(struct user_info *user,
163 struct gengetopt_args_info *ggo)
165 size_t nr_groups = ggo->gid_given + ggo->groupname_given;
167 if (ggo->uid_given && ggo->username_given) {
168 fputs("--uid and --username are mutually exclusive\n", stderr);
169 exit(EXIT_FAILURE);
172 if (ggo->uid_given)
173 user->uid = ggo->uid_arg;
174 else if (ggo->username_given)
175 user->uid = name_to_uid(ggo->username_arg);
176 else
177 user->uid = -1;
179 if (nr_groups)
180 user->groups = xmalloc(nr_groups * sizeof(gid_t));
181 else
182 user->groups = NULL;
183 user->nr_groups = 0;
185 if (ggo->gid_given) {
186 memcpy(user->groups, ggo->gid_arg,
187 ggo->gid_given * sizeof(gid_t));
188 user->nr_groups = ggo->gid_given;
191 if (ggo->groupname_given) {
192 int i;
193 for (i = 0; i < ggo->groupname_given; i++) {
194 gid_t gid = name_to_gid(ggo->groupname_arg[i]);
195 user->groups[user->nr_groups] = gid;
196 user->nr_groups++;
200 if (user->nr_groups)
201 user->gid = user->groups[0];
202 else {
203 user->gid = user->uid;
204 if (user->uid != -1) {
205 user->nr_groups = 1;
206 user->groups = &user->uid;
212 * Change the UID and GID if requested.
214 static void change_id(struct user_info *user)
216 if (user->nr_groups) {
217 if (setgroups(user->nr_groups, user->groups) < 0) {
218 perror("setgroups");
219 exit(EXIT_FAILURE);
223 if (user->gid != -1) {
224 if (setgid(user->gid) < 0) {
225 perror("setgid");
226 exit(EXIT_FAILURE);
230 if (user->uid != -1) {
231 if (setuid(user->uid) < 0) {
232 perror("setuid");
233 exit(EXIT_FAILURE);
239 * Run as a daemon
241 static void daemonize(void)
243 int fd;
245 switch (fork()) {
246 case -1:
247 perror("fork");
248 exit(EXIT_FAILURE);
249 case 0:
250 break;
251 default:
252 exit(EXIT_SUCCESS);
255 if (setsid() < 0)
256 perror("setsid");
258 if (chdir("/") < 0)
259 perror("chdir(\"/\")");
261 fd = open("/dev/null", O_RDWR, 0);
262 if (fd >= 0) {
263 if (dup2(fd, STDIN_FILENO) < 0)
264 perror("dup2(fd, STDIN_FILENO)");
265 if (dup2(fd, STDOUT_FILENO) < 0)
266 perror("dup2(fd, STDOUT_FILENO)");
267 if (dup2(fd, STDERR_FILENO) < 0)
268 perror("dup2(fd, STDERR_FILENO)");
269 if (fd > STDERR_FILENO)
270 close(fd);
271 } else
272 perror("open(\"/dev/null\")");
276 * The new process in its own namespace can now execute the command passed as
277 * argument or a shell
279 static int child_process(void *ggo_arg)
281 struct gengetopt_args_info *ggo = ggo_arg;
282 struct user_info user;
284 if (ggo->nice_given)
285 if (setpriority(PRIO_PROCESS, 0, ggo->nice_arg) < 0) {
286 perror("setpriority");
287 return EXIT_FAILURE;
290 get_user_infos(&user, ggo);
291 perform_chroot(ggo);
292 change_id(&user);
294 if (ggo->daemonize_flag)
295 daemonize();
297 if (ggo->alarm_given)
298 alarm(ggo->alarm_arg);
300 if (ggo->inputs_num) {
301 char **argv = xmalloc(sizeof(char *) * (ggo->inputs_num + 1));
302 memcpy(argv, ggo->inputs, ggo->inputs_num * sizeof(char *));
303 argv[ggo->inputs_num] = NULL;
304 execvp(argv[0], argv);
305 perror("execvp");
306 } else {
307 char *argv[] = { NULL, NULL };
308 char *shell = getenv("SHELL");
309 if (!shell)
310 shell = "/bin/sh";
311 argv[0] = shell;
312 execv(argv[0], argv);
313 perror("execv");
316 return EXIT_FAILURE;
319 int main(int argc, char *argv[])
321 struct gengetopt_args_info args_info;
322 int ret;
323 int flags;
324 int status;
326 ret = cmdline_parser(argc, argv, &args_info);
327 if (ret)
328 return ret;
330 flags = get_flags(&args_info);
331 if (argc - args_info.inputs_num <= 1) {
332 fputs("No flag specified, exiting\n", stderr);
333 return EXIT_FAILURE;
336 if (args_info.alarm_given &&
337 (args_info.alarm_arg < 0 || args_info.alarm_arg > 65535)) {
338 fprintf(stderr,
339 "The alarm value must be between 0 and 65535 got: %d\n",
340 args_info.alarm_arg);
341 return EXIT_FAILURE;
344 flags |= SIGCHLD;
345 ret = clone(child_process, &stack[STACK_SIZE - 1], flags, &args_info);
346 if (ret < 0) {
347 perror("clone");
348 return EXIT_FAILURE;
351 ret = waitpid(ret, &status, 0);
352 if (ret < 0) {
353 perror("waitpid");
354 return EXIT_FAILURE;
357 ret = WEXITSTATUS(status);
358 return ret;