1/*
2 kdesu_stub.c: KDE su executes this stub through su or ssh. This stub in turn
3 executes the target program. Before that, startup parameters
4 are sent through stdin.
5
6 This file is part of the KDE project, module kdesu.
7 SPDX-FileCopyrightText: 1999, 2000 Geert Jansen <jansen@kde.org>
8
9 SPDX-License-Identifier: LGPL-2.0-or-later
10
11
12 Available parameters:
13
14 Parameter Description Format (csl = comma separated list)
15
16 - kdesu_stub Header "ok" | "stop"
17 - display X11 display string
18 - display_auth X11 authentication "type cookie" pair
19 - command Command to run string
20 - path PATH env. var string
21 - build_sycoca Rebuild sycoca? "yes" | "no"
22 - user Target user string
23 - priority Process priority 0 <= int <= 100
24 - scheduler Process scheduler "fifo" | "normal"
25 - app_startup_id DESKTOP_STARTUP_ID string
26 - environment Additional envvars strings, last one is empty
27*/
28
29#include <config-kdesu.h>
30
31#include <errno.h>
32#include <pwd.h>
33#include <signal.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <termios.h>
38#include <unistd.h>
39
40#if HAVE_INITGROUPS
41#include <grp.h>
42#endif
43
44#include <sys/resource.h>
45#include <sys/stat.h>
46#include <sys/time.h>
47#include <sys/types.h>
48#include <sys/wait.h>
49
50#ifdef POSIX1B_SCHEDULING
51#include <sched.h>
52#endif
53
54/**
55 * Params sent by the peer.
56 */
57
58struct param_struct {
59 const char *name;
60 char *value;
61};
62
63struct param_struct params[] = {{"kdesu_stub", 0L},
64 {"display", 0L},
65 {"display_auth", 0L},
66 {"command", 0L},
67 {"path", 0L},
68 {"xwindows_only", 0L},
69 {"user", 0L},
70 {"priority", 0L},
71 {"scheduler", 0L},
72 /* obsoleted by app_startup_id { "app_start_pid", 0L } */
73 {"app_startup_id", 0L}};
74
75#define P_HEADER 0
76#define P_DISPLAY 1
77#define P_DISPLAY_AUTH 2
78#define P_COMMAND 3
79#define P_PATH 4
80#define P_XWIN_ONLY 5
81#define P_USER 6
82#define P_PRIORITY 7
83#define P_SCHEDULER 8
84#define P_APP_STARTUP_ID 9
85#define P_LAST 10
86
87/**
88 * Safe malloc functions.
89 */
90char *xmalloc(size_t size)
91{
92 char *ptr = malloc(size: size);
93 if (ptr) {
94 return ptr;
95 }
96 perror(s: "malloc()");
97 exit(status: 1);
98}
99
100char **xrealloc(char **ptr, int size)
101{
102 ptr = realloc(ptr: ptr, size: size);
103 if (ptr) {
104 return ptr;
105 }
106 perror(s: "realloc()");
107 exit(status: 1);
108}
109
110/**
111 * Solaris does not have a setenv()...
112 */
113int xsetenv(const char *name, const char *value)
114{
115 char *s = malloc(size: strlen(s: name) + strlen(s: value) + 2);
116 if (!s) {
117 return -1;
118 }
119 strcpy(dest: s, src: name);
120 strcat(dest: s, src: "=");
121 strcat(dest: s, src: value);
122 return putenv(string: s); /* yes: no free()! */
123}
124
125/**
126 * Safe strdup and strip newline
127 */
128char *xstrdup(char *src)
129{
130 int len = strlen(s: src);
131 char *dst = xmalloc(size: len + 1);
132 strcpy(dest: dst, src: src);
133 if (dst[len - 1] == '\n') {
134 dst[len - 1] = '\000';
135 }
136 return dst;
137}
138
139/**
140 * Split comma separated list.
141 */
142char **xstrsep(char *str)
143{
144 int i = 0;
145 int size = 10;
146 char **list = (char **)xmalloc(size: size * sizeof(char *));
147 char *ptr = str;
148 char *nptr;
149 while ((nptr = strchr(s: ptr, c: ',')) != 0L) {
150 if (i > size - 2) {
151 list = xrealloc(ptr: list, size: (size *= 2) * sizeof(char *));
152 }
153 *nptr = '\000';
154 list[i++] = ptr;
155 ptr = nptr + 1;
156 }
157 if (*ptr != '\000') {
158 list[i++] = ptr;
159 }
160 list[i] = 0L;
161 return list;
162}
163
164#define BUFSIZE 8192
165
166static void dequote(char *buf)
167{
168 char *in;
169 char *out;
170 for (in = buf, out = buf; *in; in++, out++) {
171 char c = *in;
172 if (c == '\\') {
173 c = *++in;
174 if (c == '/') {
175 *out = '\\';
176 } else {
177 *out = c - '@';
178 }
179 } else {
180 *out = c;
181 }
182 }
183 *out = 0;
184}
185
186/**
187 * The main program
188 */
189
190int main()
191{
192 char buf[BUFSIZE + 1];
193 char xauthority[200];
194 int i;
195 int prio;
196 pid_t pid;
197 FILE *fout;
198 struct passwd *pw;
199 const char *kdesu_lc_all;
200
201 xauthority[0] = '\0';
202
203 /* Get startup parameters. */
204
205 for (i = 0; i < P_LAST; i++) {
206 printf(format: "%s\n", params[i].name);
207 fflush(stdout);
208 if (fgets(s: buf, BUFSIZE, stdin) == 0L) {
209 printf(format: "end\n");
210 fflush(stdout);
211 perror(s: "kdesu_stub: fgets()");
212 exit(status: 1);
213 }
214 params[i].value = xstrdup(src: buf);
215 /* Installation check? */
216 if (i == 0 && !strcmp(s1: params[i].value, s2: "stop")) {
217 printf(format: "end\n");
218 exit(status: 0);
219 }
220 }
221 printf(format: "environment\n");
222 fflush(stdout);
223 for (;;) {
224 char *tmp;
225 if (fgets(s: buf, BUFSIZE, stdin) == 0L) {
226 printf(format: "end\n");
227 fflush(stdout);
228 perror(s: "kdesu_stub: fgets()");
229 exit(status: 1);
230 }
231 dequote(buf);
232 tmp = xstrdup(src: buf);
233 if (tmp[0] == '\0') { /* terminator */
234 break;
235 }
236 putenv(string: tmp);
237 }
238
239 printf(format: "end\n");
240 fflush(stdout);
241
242 xsetenv(name: "PATH", value: params[P_PATH].value);
243 xsetenv(name: "DESKTOP_STARTUP_ID", value: params[P_APP_STARTUP_ID].value);
244
245 kdesu_lc_all = getenv(name: "KDESU_LC_ALL");
246 if (kdesu_lc_all != NULL) {
247 xsetenv(name: "LC_ALL", value: kdesu_lc_all);
248 } else {
249 unsetenv(name: "LC_ALL");
250 }
251
252 unsetenv(name: "XDG_RUNTIME_DIR");
253
254 /* Do we need to change uid? */
255
256 pw = getpwnam(name: params[P_USER].value);
257 if (pw == 0L) {
258 printf(format: "kdesu_stub: user %s does not exist!\n", params[P_USER].value);
259 exit(status: 1);
260 }
261 xsetenv(name: "HOME", value: pw->pw_dir);
262
263 /* New user won't be able to connect it anyway. */
264 unsetenv(name: "DBUS_SESSION_BUS_ADDRESS");
265
266 /* Set scheduling/priority */
267
268 prio = atoi(nptr: params[P_PRIORITY].value);
269 if (!strcmp(s1: params[P_SCHEDULER].value, s2: "realtime")) {
270#ifdef POSIX1B_SCHEDULING
271 struct sched_param sched;
272 int min = sched_get_priority_min(SCHED_FIFO);
273 int max = sched_get_priority_max(SCHED_FIFO);
274 sched.sched_priority = min + (int)(((double)prio) * (max - min) / 100 + 0.5);
275 sched_setscheduler(0, SCHED_FIFO, &sched);
276#else
277 printf(format: "kdesu_stub: realtime scheduling not supported\n");
278#endif
279 } else {
280#if HAVE_SETPRIORITY
281 int val = 20 - (int)(((double)prio) * 40 / 100 + 0.5);
282 setpriority(PRIO_PROCESS, who: getpid(), prio: val);
283#endif
284 }
285
286 /* Drop privileges (this is permanent) */
287
288 if (getuid() != pw->pw_uid) {
289 if (setgid(pw->pw_gid) == -1) {
290 perror(s: "kdesu_stub: setgid()");
291 exit(status: 1);
292 }
293#if HAVE_INITGROUPS
294 if (initgroups(user: pw->pw_name, group: pw->pw_gid) == -1) {
295 perror(s: "kdesu_stub: initgroups()");
296 exit(status: 1);
297 }
298#endif
299 if (setuid(pw->pw_uid) == -1) {
300 perror(s: "kdesu_stub: setuid()");
301 exit(status: 1);
302 }
303 xsetenv(name: "HOME", value: pw->pw_dir);
304 }
305
306 /* Handle display */
307
308 if (strcmp(s1: params[P_DISPLAY].value, s2: "no")) {
309 xsetenv(name: "DISPLAY", value: params[P_DISPLAY].value);
310 if (params[P_DISPLAY_AUTH].value[0]) {
311 int fd2;
312 /*
313 ** save umask and set to 077, so we create those files only
314 ** readable for root. (if someone else could read them, we
315 ** are in deep shit).
316 */
317 int oldumask = umask(mask: 077);
318 const char *disp = params[P_DISPLAY].value;
319 if (strncmp(s1: disp, s2: "localhost:", n: 10) == 0) {
320 disp += 9;
321 }
322
323 strcpy(dest: xauthority, src: "/tmp/xauth.XXXXXXXXXX");
324 fd2 = mkstemp(template: xauthority);
325 umask(mask: oldumask);
326
327 if (fd2 == -1) {
328 perror(s: "kdesu_stub: mkstemp()");
329 exit(status: 1);
330 } else {
331 close(fd: fd2);
332 }
333 xsetenv(name: "XAUTHORITY", value: xauthority);
334
335 fout = popen(command: "xauth >/dev/null 2>&1", modes: "w");
336 if (fout == NULL) {
337 perror(s: "kdesu_stub: popen(xauth)");
338 exit(status: 1);
339 }
340 fprintf(stream: fout, format: "add %s %s\n", disp, params[P_DISPLAY_AUTH].value);
341 pclose(stream: fout);
342 }
343 }
344
345 /* Rebuild the sycoca and start kdeinit? */
346
347 if (strcmp(s1: params[P_XWIN_ONLY].value, s2: "no")) {
348 system(command: "kdeinit5 --suicide");
349 }
350
351 /* Execute the command */
352
353 pid = fork();
354 if (pid == -1) {
355 perror(s: "kdesu_stub: fork()");
356 exit(status: 1);
357 }
358 if (pid) {
359 /* Parent: wait for child, delete tempfiles and return. */
360 int ret;
361 int state;
362 int xit = 1;
363 while (1) {
364 ret = waitpid(pid: pid, stat_loc: &state, options: 0);
365 if (ret == -1) {
366 if (errno == EINTR) {
367 continue;
368 }
369 if (errno != ECHILD) {
370 perror(s: "kdesu_stub: waitpid()");
371 }
372 break;
373 }
374 if (WIFEXITED(state)) {
375 xit = WEXITSTATUS(state);
376 }
377 }
378
379 if (*xauthority) {
380 unlink(name: xauthority);
381 }
382 exit(status: xit);
383 } else {
384 setsid();
385 /* Child: exec command. */
386 sprintf(s: buf, format: "%s", params[P_COMMAND].value);
387 dequote(buf);
388 execl(path: "/bin/sh", arg: "sh", "-c", buf, (void *)0);
389 perror(s: "kdesu_stub: exec()");
390 _exit(status: 1);
391 }
392}
393

source code of kdesu/src/kdesu_stub.c