1/*
2 * $Id: sucap.c,v 1.1.1.1 1999/04/17 22:16:31 morgan Exp $
3 *
4 * This was written by Finn Arne Gangstad <finnag@guardian.no>
5 *
6 * This is a program that is intended to exec a subsequent program.
7 * The purpose of this 'sucap' wrapper is to change uid but keep all
8 * privileges. All environment variables are inherited.
9 */
10
11#include <sys/types.h>
12#include <errno.h>
13#include <stdio.h>
14#undef _POSIX_SOURCE
15#include <sys/capability.h>
16#include <pwd.h>
17#define __USE_BSD
18#include <grp.h>
19#include <unistd.h>
20#include <sys/wait.h>
21#include <errno.h>
22#include <string.h>
23#include <stdlib.h>
24
25static void usage(void)
26{
27    fprintf(stderr,
28"usage: sucap <user> <group> <command-path> [command-args...]\n\n"
29"  This program is a wrapper that change UID but not privileges of a\n"
30"  program to be executed.\n"
31"  Note, this wrapper is intended to assist in overcoming a lack of support\n"
32"  for filesystem capability attributes and should be used to launch other\n"
33"  files. This program should _NOT_ be made setuid-0.\n\n"
34"[Copyright (c) 1998 Finn Arne Gangstad <finnag@guardian.no>]\n");
35
36    exit(1);
37}
38
39
40static void
41wait_on_fd(int fd)
42{
43    /* Wait until some data is available on a file descriptor, or until
44     * end of file or an error is detected */
45    char buf[1];
46    while (read(fd, buf, sizeof(buf)) == -1 && errno == EINTR) {
47	/* empty loop */
48    }
49}
50
51
52int main(int argc, char **argv)
53{
54    cap_t old_caps;
55    uid_t uid;
56    pid_t pid, parent_pid;
57    gid_t gid;
58    int pipe_fds[2];
59
60    /* this program should not be made setuid-0 */
61    if (getuid() && !geteuid()) {
62        usage();
63    }
64
65    /* check that we have at least 3 arguments */
66    if (argc < 4) {
67        usage();
68    }
69
70    /* Convert username to uid */
71    {
72	struct passwd *pw = getpwnam(argv[1]);
73	if (!pw) {
74	    fprintf(stderr, "sucap: No such user: %s\n", argv[1]);
75	    exit(1);
76	}
77	uid = pw->pw_uid;
78    }
79
80    /* Convert groupname to gid */
81    {
82	struct group *gr = getgrnam(argv[2]);
83	if (!gr) {
84	    fprintf(stderr, "sucap: No such group: %s\n", argv[2]);
85	    exit(1);
86	}
87	gid = gr->gr_gid;
88    }
89
90    /* set process group to current pid */
91    if (setpgid(0, getpid())) {
92	perror("sucap: Failed to set process group");
93	exit(1);
94    }
95
96    if (pipe(pipe_fds)) {
97	perror("sucap: pipe() failed");
98	exit(1);
99    }
100
101    parent_pid = getpid();
102
103    old_caps = cap_init();
104    if (capgetp(0, old_caps)) {
105	perror("sucap: capgetp");
106	exit(1);
107    }
108
109    {
110	ssize_t x;
111	printf("Caps: %s\n", cap_to_text(old_caps, &x));
112    }
113
114
115    /* fork off a child to do the hard work */
116    fflush(NULL);
117    pid = fork();
118    if (pid == -1) {
119	perror("sucap: fork failed");
120	exit(1);
121    }
122
123    /* 1. mother process sets gid and uid
124     * 2. child process sets capabilities of mother process
125     * 3. mother process execs whatever is to be executed
126     */
127
128    if (pid) {
129	/* Mother process. */
130	close(pipe_fds[0]);
131
132	/* Get rid of any supplemental groups */
133	if (!getuid() && setgroups(0, 0)) {
134	    perror("sucap: setgroups failed");
135	    exit(1);
136	}
137
138	/* Set gid and uid (this probably clears capabilities) */
139	setregid(gid, gid);
140	setreuid(uid, uid);
141
142	{
143	    ssize_t x;
144	    cap_t cap = cap_init();
145	    capgetp(0, cap);
146	    printf("Caps: %s\n", cap_to_text(cap, &x));
147	}
148
149	printf("[debug] uid:%d, real uid:%d\n", geteuid(), getuid());
150
151	/* Signal child that we want our privileges updated */
152	close(pipe_fds[1]); /* Child hangs in blocking read */
153
154	/* Wait for child process to set our privileges */
155	{
156	    int status = 0;
157	    if (wait(&status) == -1) {
158		perror("sucap: wait failed");
159	    }
160	    if (!WIFEXITED(status) || WEXITSTATUS(status)) {
161		fprintf(stderr, "sucap: child did not exit cleanly.\n");
162		exit(1);
163	    }
164	}
165
166	{
167	    ssize_t x;
168	    cap_t cap = cap_init();
169	    capgetp(0, cap);
170	    printf("Caps: %s\n", cap_to_text(cap, &x));
171	}
172
173/*	printf("[debug] uid:%d, real uid:%d\n", geteuid(), getuid()); */
174	/* exec the program indicated by args 2 ... */
175	execvp(argv[3], argv+3);
176
177	/* if we fall through to here, our exec failed -- announce the fact */
178	fprintf(stderr, "Unable to execute command: %s\n", strerror(errno));
179
180	usage();
181    } else {
182	/* Child process */
183	close(pipe_fds[1]);
184
185	/* Wait for mother process to setuid */
186	wait_on_fd(pipe_fds[0]);
187
188	/* Set privileges on mother process */
189	if (capsetp(parent_pid, old_caps)) {
190	    perror("sucaps: capsetp");
191	    _exit(1);
192	}
193
194	/* exit to signal mother process that we are ready */
195	_exit(0);
196    }
197
198    return 0;
199}
200