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 | |
58 | struct param_struct { |
59 | const char *name; |
60 | char *value; |
61 | }; |
62 | |
63 | struct 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 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 | */ |
90 | char *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 | |
100 | char **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 | */ |
113 | int 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 | */ |
128 | char *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 | */ |
142 | char **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 | |
166 | static 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 | |
190 | int 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 | |