1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /* activation-helper.c Setuid helper for launching programs as a custom
3 * user. This file is security sensitive.
4 *
5 * Copyright (C) 2007 Red Hat, Inc.
6 *
7 * Licensed under the Academic Free License version 2.1
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 *
23 */
24
25 #include <config.h>
26
27 #include "bus.h"
28 #include "driver.h"
29 #include "utils.h"
30 #include "desktop-file.h"
31 #include "config-parser-trivial.h"
32 #include "activation-helper.h"
33 #include "activation-exit-codes.h"
34
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <sys/types.h>
40 #include <pwd.h>
41 #include <grp.h>
42
43 #include <dbus/dbus-misc.h>
44 #include <dbus/dbus-shell.h>
45 #include <dbus/dbus-marshal-validate.h>
46
47 static BusDesktopFile *
desktop_file_for_name(BusConfigParser * parser,const char * name,DBusError * error)48 desktop_file_for_name (BusConfigParser *parser,
49 const char *name,
50 DBusError *error)
51 {
52 BusDesktopFile *desktop_file;
53 DBusList **service_dirs;
54 DBusList *link;
55 DBusError tmp_error;
56 DBusString full_path;
57 DBusString filename;
58 const char *dir;
59
60 _DBUS_ASSERT_ERROR_IS_CLEAR (error);
61
62 desktop_file = NULL;
63
64 if (!_dbus_string_init (&filename))
65 {
66 BUS_SET_OOM (error);
67 goto out_all;
68 }
69
70 if (!_dbus_string_init (&full_path))
71 {
72 BUS_SET_OOM (error);
73 goto out_filename;
74 }
75
76 if (!_dbus_string_append (&filename, name) ||
77 !_dbus_string_append (&filename, ".service"))
78 {
79 BUS_SET_OOM (error);
80 goto out;
81 }
82
83 service_dirs = bus_config_parser_get_service_paths (parser);
84 for (link = _dbus_list_get_first_link (service_dirs);
85 link != NULL;
86 link = _dbus_list_get_next_link (service_dirs, link))
87 {
88 dir = link->data;
89 _dbus_verbose ("Looking at '%s'\n", dir);
90
91 dbus_error_init (&tmp_error);
92
93 /* clear the path from last time */
94 _dbus_string_set_length (&full_path, 0);
95
96 /* build the full path */
97 if (!_dbus_string_append (&full_path, dir) ||
98 !_dbus_concat_dir_and_file (&full_path, &filename))
99 {
100 BUS_SET_OOM (error);
101 goto out;
102 }
103
104 _dbus_verbose ("Trying to load file '%s'\n", _dbus_string_get_data (&full_path));
105 desktop_file = bus_desktop_file_load (&full_path, &tmp_error);
106 if (desktop_file == NULL)
107 {
108 _DBUS_ASSERT_ERROR_IS_SET (&tmp_error);
109 _dbus_verbose ("Could not load %s: %s: %s\n",
110 _dbus_string_get_const_data (&full_path),
111 tmp_error.name, tmp_error.message);
112
113 /* we may have failed if the file is not found; this is not fatal */
114 if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY))
115 {
116 dbus_move_error (&tmp_error, error);
117 /* we only bail out on OOM */
118 goto out;
119 }
120 dbus_error_free (&tmp_error);
121 }
122
123 /* did we find the desktop file we want? */
124 if (desktop_file != NULL)
125 break;
126 }
127
128 /* Didn't find desktop file; set error */
129 if (desktop_file == NULL)
130 {
131 dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND,
132 "The name %s was not provided by any .service files",
133 name);
134 }
135
136 out:
137 _dbus_string_free (&full_path);
138 out_filename:
139 _dbus_string_free (&filename);
140 out_all:
141 return desktop_file;
142 }
143
144 /* Clears the environment, except for DBUS_STARTER_x,
145 * which we hardcode to the system bus.
146 */
147 static dbus_bool_t
clear_environment(DBusError * error)148 clear_environment (DBusError *error)
149 {
150 #ifndef ACTIVATION_LAUNCHER_TEST
151 /* totally clear the environment */
152 if (!_dbus_clearenv ())
153 {
154 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
155 "could not clear environment\n");
156 return FALSE;
157 }
158
159 /* Ensure the bus is set to system */
160 dbus_setenv ("DBUS_STARTER_ADDRESS", DBUS_SYSTEM_BUS_DEFAULT_ADDRESS);
161 dbus_setenv ("DBUS_STARTER_BUS_TYPE", "system");
162 #endif
163
164 return TRUE;
165 }
166
167 static dbus_bool_t
check_permissions(const char * dbus_user,DBusError * error)168 check_permissions (const char *dbus_user, DBusError *error)
169 {
170 #ifndef ACTIVATION_LAUNCHER_TEST
171 uid_t uid, euid;
172 struct passwd *pw;
173
174 pw = NULL;
175 uid = 0;
176 euid = 0;
177
178 /* bail out unless the dbus user is invoking the helper */
179 pw = getpwnam(dbus_user);
180 if (!pw)
181 {
182 dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,
183 "cannot find user '%s'", dbus_user);
184 return FALSE;
185 }
186 uid = getuid();
187 if (pw->pw_uid != uid)
188 {
189 dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,
190 "not invoked from user '%s'", dbus_user);
191 return FALSE;
192 }
193
194 /* bail out unless we are setuid to user root */
195 euid = geteuid();
196 if (euid != 0)
197 {
198 dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,
199 "not setuid root");
200 return FALSE;
201 }
202 #endif
203
204 return TRUE;
205 }
206
207 static dbus_bool_t
check_service_name(BusDesktopFile * desktop_file,const char * service_name,DBusError * error)208 check_service_name (BusDesktopFile *desktop_file,
209 const char *service_name,
210 DBusError *error)
211 {
212 char *name_tmp;
213 dbus_bool_t retval;
214
215 retval = FALSE;
216
217 /* try to get Name */
218 if (!bus_desktop_file_get_string (desktop_file,
219 DBUS_SERVICE_SECTION,
220 DBUS_SERVICE_NAME,
221 &name_tmp,
222 error))
223 goto failed;
224
225 /* verify that the name is the same as the file service name */
226 if (strcmp (service_name, name_tmp) != 0)
227 {
228 dbus_set_error (error, DBUS_ERROR_SPAWN_FILE_INVALID,
229 "Service '%s' does not match expected value", name_tmp);
230 goto failed_free;
231 }
232
233 retval = TRUE;
234
235 failed_free:
236 /* we don't return the name, so free it here */
237 dbus_free (name_tmp);
238 failed:
239 return retval;
240 }
241
242 static dbus_bool_t
get_parameters_for_service(BusDesktopFile * desktop_file,const char * service_name,char ** exec,char ** user,DBusError * error)243 get_parameters_for_service (BusDesktopFile *desktop_file,
244 const char *service_name,
245 char **exec,
246 char **user,
247 DBusError *error)
248 {
249 char *exec_tmp;
250 char *user_tmp;
251
252 exec_tmp = NULL;
253 user_tmp = NULL;
254
255 /* check the name of the service */
256 if (!check_service_name (desktop_file, service_name, error))
257 goto failed;
258
259 /* get the complete path of the executable */
260 if (!bus_desktop_file_get_string (desktop_file,
261 DBUS_SERVICE_SECTION,
262 DBUS_SERVICE_EXEC,
263 &exec_tmp,
264 error))
265 {
266 _DBUS_ASSERT_ERROR_IS_SET (error);
267 goto failed;
268 }
269
270 /* get the user that should run this service - user is compulsary for system activation */
271 if (!bus_desktop_file_get_string (desktop_file,
272 DBUS_SERVICE_SECTION,
273 DBUS_SERVICE_USER,
274 &user_tmp,
275 error))
276 {
277 _DBUS_ASSERT_ERROR_IS_SET (error);
278 goto failed;
279 }
280
281 /* only assign if all the checks passed */
282 *exec = exec_tmp;
283 *user = user_tmp;
284 return TRUE;
285
286 failed:
287 dbus_free (exec_tmp);
288 dbus_free (user_tmp);
289 return FALSE;
290 }
291
292 static dbus_bool_t
switch_user(char * user,DBusError * error)293 switch_user (char *user, DBusError *error)
294 {
295 #ifndef ACTIVATION_LAUNCHER_TEST
296 struct passwd *pw;
297
298 /* find user */
299 pw = getpwnam (user);
300 if (!pw)
301 {
302 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
303 "cannot find user '%s'\n", user);
304 return FALSE;
305 }
306
307 /* initialize the group access list */
308 if (initgroups (user, pw->pw_gid))
309 {
310 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
311 "could not initialize groups");
312 return FALSE;
313 }
314
315 /* change to the primary group for the user */
316 if (setgid (pw->pw_gid))
317 {
318 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
319 "cannot setgid group %i", pw->pw_gid);
320 return FALSE;
321 }
322
323 /* change to the user specified */
324 if (setuid (pw->pw_uid) < 0)
325 {
326 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
327 "cannot setuid user %i", pw->pw_uid);
328 return FALSE;
329 }
330 #endif
331 return TRUE;
332 }
333
334 static dbus_bool_t
exec_for_correct_user(char * exec,char * user,DBusError * error)335 exec_for_correct_user (char *exec, char *user, DBusError *error)
336 {
337 char **argv;
338 int argc;
339 dbus_bool_t retval;
340
341 argc = 0;
342 retval = TRUE;
343 argv = NULL;
344
345 if (!switch_user (user, error))
346 return FALSE;
347
348 /* convert command into arguments */
349 if (!_dbus_shell_parse_argv (exec, &argc, &argv, error))
350 return FALSE;
351
352 #ifndef ACTIVATION_LAUNCHER_DO_OOM
353 /* replace with new binary, with no environment */
354 if (execv (argv[0], argv) < 0)
355 {
356 dbus_set_error (error, DBUS_ERROR_SPAWN_EXEC_FAILED,
357 "Failed to exec: %s", argv[0]);
358 retval = FALSE;
359 }
360 #endif
361
362 dbus_free_string_array (argv);
363 return retval;
364 }
365
366 static dbus_bool_t
check_bus_name(const char * bus_name,DBusError * error)367 check_bus_name (const char *bus_name,
368 DBusError *error)
369 {
370 DBusString str;
371
372 _dbus_string_init_const (&str, bus_name);
373 if (!_dbus_validate_bus_name (&str, 0, _dbus_string_get_length (&str)))
374 {
375 dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_INVALID,
376 "bus name '%s' is not a valid bus name\n",
377 bus_name);
378 return FALSE;
379 }
380
381 return TRUE;
382 }
383
384 static dbus_bool_t
get_correct_parser(BusConfigParser ** parser,DBusError * error)385 get_correct_parser (BusConfigParser **parser, DBusError *error)
386 {
387 DBusString config_file;
388 dbus_bool_t retval;
389 #ifdef ACTIVATION_LAUNCHER_TEST
390 const char *test_config_file;
391 #endif
392
393 retval = FALSE;
394
395 #ifdef ACTIVATION_LAUNCHER_TEST
396 test_config_file = NULL;
397
398 /* there is no _way_ we should be setuid if this define is set.
399 * but we should be doubly paranoid and check... */
400 if (getuid() != geteuid())
401 _dbus_assert_not_reached ("dbus-daemon-launch-helper-test binary is setuid!");
402
403 /* this is not a security hole. The environment variable is only passed in the
404 * dbus-daemon-lauch-helper-test NON-SETUID launcher */
405 test_config_file = _dbus_getenv ("TEST_LAUNCH_HELPER_CONFIG");
406 if (test_config_file == NULL)
407 {
408 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
409 "the TEST_LAUNCH_HELPER_CONFIG env variable is not set");
410 goto out;
411 }
412 #endif
413
414 /* we _only_ use the predefined system config file */
415 if (!_dbus_string_init (&config_file))
416 {
417 BUS_SET_OOM (error);
418 goto out;
419 }
420 #ifndef ACTIVATION_LAUNCHER_TEST
421 if (!_dbus_string_append (&config_file, DBUS_SYSTEM_CONFIG_FILE))
422 {
423 BUS_SET_OOM (error);
424 goto out_free_config;
425 }
426 #else
427 if (!_dbus_string_append (&config_file, test_config_file))
428 {
429 BUS_SET_OOM (error);
430 goto out_free_config;
431 }
432 #endif
433
434 /* where are we pointing.... */
435 _dbus_verbose ("dbus-daemon-activation-helper: using config file: %s\n",
436 _dbus_string_get_const_data (&config_file));
437
438 /* get the dbus user */
439 *parser = bus_config_load (&config_file, TRUE, NULL, error);
440 if (*parser == NULL)
441 {
442 goto out_free_config;
443 }
444
445 /* woot */
446 retval = TRUE;
447
448 out_free_config:
449 _dbus_string_free (&config_file);
450 out:
451 return retval;
452 }
453
454 static dbus_bool_t
launch_bus_name(const char * bus_name,BusConfigParser * parser,DBusError * error)455 launch_bus_name (const char *bus_name, BusConfigParser *parser, DBusError *error)
456 {
457 BusDesktopFile *desktop_file;
458 char *exec, *user;
459 dbus_bool_t retval;
460
461 exec = NULL;
462 user = NULL;
463 retval = FALSE;
464
465 /* get the correct service file for the name we are trying to activate */
466 desktop_file = desktop_file_for_name (parser, bus_name, error);
467 if (desktop_file == NULL)
468 return FALSE;
469
470 /* get exec and user for service name */
471 if (!get_parameters_for_service (desktop_file, bus_name, &exec, &user, error))
472 goto finish;
473
474 _dbus_verbose ("dbus-daemon-activation-helper: Name='%s'\n", bus_name);
475 _dbus_verbose ("dbus-daemon-activation-helper: Exec='%s'\n", exec);
476 _dbus_verbose ("dbus-daemon-activation-helper: User='%s'\n", user);
477
478 /* actually execute */
479 if (!exec_for_correct_user (exec, user, error))
480 goto finish;
481
482 retval = TRUE;
483
484 finish:
485 dbus_free (exec);
486 dbus_free (user);
487 bus_desktop_file_free (desktop_file);
488 return retval;
489 }
490
491 static dbus_bool_t
check_dbus_user(BusConfigParser * parser,DBusError * error)492 check_dbus_user (BusConfigParser *parser, DBusError *error)
493 {
494 const char *dbus_user;
495
496 dbus_user = bus_config_parser_get_user (parser);
497 if (dbus_user == NULL)
498 {
499 dbus_set_error (error, DBUS_ERROR_SPAWN_CONFIG_INVALID,
500 "could not get user from config file\n");
501 return FALSE;
502 }
503
504 /* check to see if permissions are correct */
505 if (!check_permissions (dbus_user, error))
506 return FALSE;
507
508 return TRUE;
509 }
510
511 dbus_bool_t
run_launch_helper(const char * bus_name,DBusError * error)512 run_launch_helper (const char *bus_name,
513 DBusError *error)
514 {
515 BusConfigParser *parser;
516 dbus_bool_t retval;
517
518 parser = NULL;
519 retval = FALSE;
520
521 /* clear the environment, apart from a few select settings */
522 if (!clear_environment (error))
523 goto error;
524
525 /* check to see if we have a valid bus name */
526 if (!check_bus_name (bus_name, error))
527 goto error;
528
529 /* get the correct parser, either the test or default parser */
530 if (!get_correct_parser (&parser, error))
531 goto error;
532
533 /* check we are being invoked by the correct dbus user */
534 if (!check_dbus_user (parser, error))
535 goto error_free_parser;
536
537 /* launch the bus with the service defined user */
538 if (!launch_bus_name (bus_name, parser, error))
539 goto error_free_parser;
540
541 /* woohoo! */
542 retval = TRUE;
543
544 error_free_parser:
545 bus_config_parser_unref (parser);
546 error:
547 return retval;
548 }
549
550