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
46static BusDesktopFile *
47desktop_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
135out:
136  _dbus_string_free (&full_path);
137out_filename:
138  _dbus_string_free (&filename);
139out_all:
140  return desktop_file;
141}
142
143/* Cleares the environment, except for DBUS_VERBOSE and DBUS_STARTER_x */
144static dbus_bool_t
145clear_environment (DBusError *error)
146{
147  const char *debug_env = NULL;
148  const char *starter_env = NULL;
149
150#ifdef DBUS_ENABLE_VERBOSE_MODE
151  /* are we debugging */
152  debug_env = _dbus_getenv ("DBUS_VERBOSE");
153#endif
154
155  /* we save the starter */
156  starter_env = _dbus_getenv ("DBUS_STARTER_ADDRESS");
157
158#ifndef ACTIVATION_LAUNCHER_TEST
159  /* totally clear the environment */
160  if (!_dbus_clearenv ())
161    {
162      dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
163                      "could not clear environment\n");
164      return FALSE;
165    }
166#endif
167
168#ifdef DBUS_ENABLE_VERBOSE_MODE
169  /* restore the debugging environment setting if set */
170  if (debug_env)
171    _dbus_setenv ("DBUS_VERBOSE", debug_env);
172#endif
173
174  /* restore the starter */
175  if (starter_env)
176    _dbus_setenv ("DBUS_STARTER_ADDRESS", starter_env);
177
178  /* set the type, which must be system if we got this far */
179  _dbus_setenv ("DBUS_STARTER_BUS_TYPE", "system");
180
181  return TRUE;
182}
183
184static dbus_bool_t
185check_permissions (const char *dbus_user, DBusError *error)
186{
187  uid_t uid, euid;
188  struct passwd *pw;
189
190  pw = NULL;
191  uid = 0;
192  euid = 0;
193
194#ifndef ACTIVATION_LAUNCHER_TEST
195  /* bail out unless the dbus user is invoking the helper */
196  pw = getpwnam(dbus_user);
197  if (!pw)
198    {
199      dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,
200                      "cannot find user '%s'", dbus_user);
201      return FALSE;
202    }
203  uid = getuid();
204  if (pw->pw_uid != uid)
205    {
206      dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,
207                      "not invoked from user '%s'", dbus_user);
208      return FALSE;
209    }
210
211  /* bail out unless we are setuid to user root */
212  euid = geteuid();
213  if (euid != 0)
214    {
215      dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,
216                      "not setuid root");
217      return FALSE;
218    }
219#endif
220
221  return TRUE;
222}
223
224static dbus_bool_t
225check_service_name (BusDesktopFile *desktop_file,
226                    const char     *service_name,
227                    DBusError      *error)
228{
229  char *name_tmp;
230  dbus_bool_t retval;
231
232  retval = FALSE;
233
234  /* try to get Name */
235  if (!bus_desktop_file_get_string (desktop_file,
236                                    DBUS_SERVICE_SECTION,
237                                    DBUS_SERVICE_NAME,
238                                    &name_tmp,
239                                    error))
240    goto failed;
241
242  /* verify that the name is the same as the file service name */
243  if (strcmp (service_name, name_tmp) != 0)
244    {
245      dbus_set_error (error, DBUS_ERROR_SPAWN_FILE_INVALID,
246                      "Service '%s' does not match expected value", name_tmp);
247      goto failed_free;
248    }
249
250  retval = TRUE;
251
252failed_free:
253  /* we don't return the name, so free it here */
254  dbus_free (name_tmp);
255failed:
256  return retval;
257}
258
259static dbus_bool_t
260get_parameters_for_service (BusDesktopFile *desktop_file,
261                            const char     *service_name,
262                            char          **exec,
263                            char          **user,
264                            DBusError      *error)
265{
266  char *exec_tmp;
267  char *user_tmp;
268
269  exec_tmp = NULL;
270  user_tmp = NULL;
271
272  /* check the name of the service */
273  if (!check_service_name (desktop_file, service_name, error))
274    goto failed;
275
276  /* get the complete path of the executable */
277  if (!bus_desktop_file_get_string (desktop_file,
278                                    DBUS_SERVICE_SECTION,
279                                    DBUS_SERVICE_EXEC,
280                                    &exec_tmp,
281                                    error))
282    {
283      _DBUS_ASSERT_ERROR_IS_SET (error);
284      goto failed;
285    }
286
287  /* get the user that should run this service - user is compulsary for system activation */
288  if (!bus_desktop_file_get_string (desktop_file,
289                                    DBUS_SERVICE_SECTION,
290                                    DBUS_SERVICE_USER,
291                                    &user_tmp,
292                                    error))
293    {
294      _DBUS_ASSERT_ERROR_IS_SET (error);
295      goto failed;
296    }
297
298  /* only assign if all the checks passed */
299  *exec = exec_tmp;
300  *user = user_tmp;
301  return TRUE;
302
303failed:
304  dbus_free (exec_tmp);
305  dbus_free (user_tmp);
306  return FALSE;
307}
308
309static dbus_bool_t
310switch_user (char *user, DBusError *error)
311{
312#ifndef ACTIVATION_LAUNCHER_TEST
313  struct passwd *pw;
314
315  /* find user */
316  pw = getpwnam (user);
317  if (!pw)
318    {
319      dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
320                      "cannot find user '%s'\n", user);
321      return FALSE;
322    }
323
324  /* initialize the group access list */
325  if (initgroups (user, pw->pw_gid))
326    {
327      dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
328                      "could not initialize groups");
329      return FALSE;
330    }
331
332  /* change to the primary group for the user */
333  if (setgid (pw->pw_gid))
334    {
335      dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
336                      "cannot setgid group %i", pw->pw_gid);
337      return FALSE;
338    }
339
340  /* change to the user specified */
341  if (setuid (pw->pw_uid) < 0)
342    {
343      dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
344                      "cannot setuid user %i", pw->pw_uid);
345      return FALSE;
346    }
347#endif
348  return TRUE;
349}
350
351static dbus_bool_t
352exec_for_correct_user (char *exec, char *user, DBusError *error)
353{
354  char **argv;
355  int argc;
356  dbus_bool_t retval;
357
358  argc = 0;
359  retval = TRUE;
360  argv = NULL;
361
362  if (!switch_user (user, error))
363    return FALSE;
364
365  /* convert command into arguments */
366  if (!_dbus_shell_parse_argv (exec, &argc, &argv, error))
367    return FALSE;
368
369#ifndef ACTIVATION_LAUNCHER_DO_OOM
370  /* replace with new binary, with no environment */
371  if (execv (argv[0], argv) < 0)
372    {
373      dbus_set_error (error, DBUS_ERROR_SPAWN_EXEC_FAILED,
374                      "Failed to exec: %s", argv[0]);
375      retval = FALSE;
376    }
377#endif
378
379  dbus_free_string_array (argv);
380  return retval;
381}
382
383static dbus_bool_t
384check_bus_name (const char *bus_name,
385                DBusError  *error)
386{
387  DBusString str;
388
389  _dbus_string_init_const (&str, bus_name);
390  if (!_dbus_validate_bus_name (&str, 0, _dbus_string_get_length (&str)))
391    {
392      dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND,
393                      "bus name '%s' is not a valid bus name\n",
394                      bus_name);
395      return FALSE;
396    }
397
398  return TRUE;
399}
400
401static dbus_bool_t
402get_correct_parser (BusConfigParser **parser, DBusError *error)
403{
404  DBusString config_file;
405  dbus_bool_t retval;
406  const char *test_config_file;
407
408  retval = FALSE;
409  test_config_file = NULL;
410
411#ifdef ACTIVATION_LAUNCHER_TEST
412  /* there is no _way_ we should be setuid if this define is set.
413   * but we should be doubly paranoid and check... */
414  if (getuid() != geteuid())
415    _dbus_assert_not_reached ("dbus-daemon-launch-helper-test binary is setuid!");
416
417  /* this is not a security hole. The environment variable is only passed in the
418   * dbus-daemon-lauch-helper-test NON-SETUID launcher */
419  test_config_file = _dbus_getenv ("TEST_LAUNCH_HELPER_CONFIG");
420  if (test_config_file == NULL)
421    {
422      dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
423                      "the TEST_LAUNCH_HELPER_CONFIG env variable is not set");
424      goto out;
425    }
426#endif
427
428  /* we _only_ use the predefined system config file */
429  if (!_dbus_string_init (&config_file))
430    {
431      BUS_SET_OOM (error);
432      goto out;
433    }
434#ifndef ACTIVATION_LAUNCHER_TEST
435  if (!_dbus_string_append (&config_file, DBUS_SYSTEM_CONFIG_FILE))
436    {
437      BUS_SET_OOM (error);
438      goto out_free_config;
439    }
440#else
441  if (!_dbus_string_append (&config_file, test_config_file))
442    {
443      BUS_SET_OOM (error);
444      goto out_free_config;
445    }
446#endif
447
448  /* where are we pointing.... */
449  _dbus_verbose ("dbus-daemon-activation-helper: using config file: %s\n",
450                 _dbus_string_get_const_data (&config_file));
451
452  /* get the dbus user */
453  *parser = bus_config_load (&config_file, TRUE, NULL, error);
454  if (*parser == NULL)
455    {
456      goto out_free_config;
457    }
458
459  /* woot */
460  retval = TRUE;
461
462out_free_config:
463  _dbus_string_free (&config_file);
464out:
465  return retval;
466}
467
468static dbus_bool_t
469launch_bus_name (const char *bus_name, BusConfigParser *parser, DBusError *error)
470{
471  BusDesktopFile *desktop_file;
472  char *exec, *user;
473  dbus_bool_t retval;
474
475  exec = NULL;
476  user = NULL;
477  retval = FALSE;
478
479  /* get the correct service file for the name we are trying to activate */
480  desktop_file = desktop_file_for_name (parser, bus_name, error);
481  if (desktop_file == NULL)
482    return FALSE;
483
484  /* get exec and user for service name */
485  if (!get_parameters_for_service (desktop_file, bus_name, &exec, &user, error))
486    goto finish;
487
488  _dbus_verbose ("dbus-daemon-activation-helper: Name='%s'\n", bus_name);
489  _dbus_verbose ("dbus-daemon-activation-helper: Exec='%s'\n", exec);
490  _dbus_verbose ("dbus-daemon-activation-helper: User='%s'\n", user);
491
492  /* actually execute */
493  if (!exec_for_correct_user (exec, user, error))
494    goto finish;
495
496  retval = TRUE;
497
498finish:
499  dbus_free (exec);
500  dbus_free (user);
501  bus_desktop_file_free (desktop_file);
502  return retval;
503}
504
505static dbus_bool_t
506check_dbus_user (BusConfigParser *parser, DBusError *error)
507{
508  const char *dbus_user;
509
510  dbus_user = bus_config_parser_get_user (parser);
511  if (dbus_user == NULL)
512    {
513      dbus_set_error (error, DBUS_ERROR_SPAWN_CONFIG_INVALID,
514                      "could not get user from config file\n");
515      return FALSE;
516    }
517
518  /* check to see if permissions are correct */
519  if (!check_permissions (dbus_user, error))
520    return FALSE;
521
522  return TRUE;
523}
524
525dbus_bool_t
526run_launch_helper (const char *bus_name,
527                   DBusError  *error)
528{
529  BusConfigParser *parser;
530  dbus_bool_t retval;
531
532  parser = NULL;
533  retval = FALSE;
534
535  /* clear the environment, apart from a few select settings */
536  if (!clear_environment (error))
537    goto error;
538
539  /* check to see if we have a valid bus name */
540  if (!check_bus_name (bus_name, error))
541    goto error;
542
543  /* get the correct parser, either the test or default parser */
544  if (!get_correct_parser (&parser, error))
545    goto error;
546
547  /* check we are being invoked by the correct dbus user */
548  if (!check_dbus_user (parser, error))
549    goto error_free_parser;
550
551  /* launch the bus with the service defined user */
552  if (!launch_bus_name (bus_name, parser, error))
553    goto error_free_parser;
554
555  /* woohoo! */
556  retval = TRUE;
557
558error_free_parser:
559  bus_config_parser_unref (parser);
560error:
561  return retval;
562}
563
564