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