pkexec.c 21.3 KB
Newer Older
David Zeuthen's avatar
David Zeuthen committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/*
 * Copyright (C) 2008 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Author: David Zeuthen <davidz@redhat.com>
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

26 27
#define _GNU_SOURCE

David Zeuthen's avatar
David Zeuthen committed
28 29 30 31 32
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
33
#include <fcntl.h>
David Zeuthen's avatar
David Zeuthen committed
34 35 36
#include <grp.h>
#include <pwd.h>
#include <errno.h>
37
#include <security/pam_appl.h>
38 39
#include <syslog.h>
#include <stdarg.h>
David Zeuthen's avatar
David Zeuthen committed
40

41 42 43 44
#ifdef __linux__
#include <sys/prctl.h>
#endif

David Zeuthen's avatar
David Zeuthen committed
45 46
#include <polkit/polkit.h>

47
static gchar *original_user_name = NULL;
48
static gchar *original_cwd = NULL;
49 50 51
static gchar *command_line = NULL;
static struct passwd *pw;

52 53 54 55 56 57 58 59 60 61 62 63
#ifndef HAVE_CLEARENV
extern char **environ;

static int
clearenv (void)
{
        if (environ != NULL)
                environ[0] = NULL;
        return 0;
}
#endif

David Zeuthen's avatar
David Zeuthen committed
64 65 66
static void
usage (int argc, char *argv[])
{
67 68 69 70 71
  g_printerr ("pkexec --version |\n"
              "       --help |\n"
              "       [--user username] PROGRAM [ARGUMENTS...]\n"
              "\n"
              "See the pkexec manual page for more details.\n");
David Zeuthen's avatar
David Zeuthen committed
72 73 74 75
}

/* ---------------------------------------------------------------------------------------------------- */

76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
static void
log_message (gint     level,
             gboolean print_to_stderr,
             const    gchar *format,
             ...)
{
  static gboolean is_log_open = FALSE;
  va_list var_args;
  gchar *s;
  const gchar *tty;

  if (!is_log_open)
    {
      openlog ("pkexec",
               LOG_PID,
               LOG_AUTHPRIV); /* security/authorization messages (private) */
      is_log_open = TRUE;
    }

  va_start (var_args, format);
  s = g_strdup_vprintf (format, var_args);
  va_end (var_args);

  tty = ttyname (0);
  if (tty == NULL)
    tty = "unknown";

  /* first complain to syslog */
  syslog (level,
          "%s: %s [USER=%s] [TTY=%s] [CWD=%s] [COMMAND=%s]",
          original_user_name,
          s,
          pw->pw_name,
          tty,
110
          original_cwd,
111 112 113 114 115 116 117 118 119 120 121
          command_line);

  /* and then on stderr */
  if (print_to_stderr)
    g_printerr ("%s\n", s);

  g_free (s);
}

/* ---------------------------------------------------------------------------------------------------- */

122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
static int
pam_conversation_function (int n,
                           const struct pam_message **msg,
                           struct pam_response **resp,
                           void *data)
{
  g_assert_not_reached ();
  return PAM_CONV_ERR;
}

static gboolean
open_session (const gchar *user_to_auth)
{
  gboolean ret;
  gint rc;
  pam_handle_t *pam_h;
  struct pam_conv conversation;

  ret = FALSE;

  pam_h = NULL;

  conversation.conv        = pam_conversation_function;
  conversation.appdata_ptr = NULL;

  /* start the pam stack */
  rc = pam_start ("polkit-1",
                  user_to_auth,
                  &conversation,
                  &pam_h);
  if (rc != PAM_SUCCESS)
    {
      g_printerr ("pam_start() failed: %s\n", pam_strerror (pam_h, rc));
      goto out;
    }

  /* open a session */
  rc = pam_open_session (pam_h,
                         0); /* flags */
  if (rc != PAM_SUCCESS)
    {
      g_printerr ("pam_open_session() failed: %s\n", pam_strerror (pam_h, rc));
      goto out;
    }

  ret = TRUE;

out:
  if (pam_h != NULL)
    pam_end (pam_h, rc);
  return ret;
}

/* ---------------------------------------------------------------------------------------------------- */

177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
typedef gboolean (*FdCallback) (gint fd, gpointer user_data);

static gboolean
set_close_on_exec (gint     fd,
                   gpointer user_data)
{
  gint fd_bottom;

  fd_bottom = GPOINTER_TO_INT (user_data);

  if (fd >= fd_bottom)
    {
      if (fcntl (fd, F_SETFD, FD_CLOEXEC) != 0 && errno != EBADF)
        {
          return FALSE;
        }
    }

  return TRUE;
}

static gboolean
fdwalk (FdCallback callback,
        gpointer   user_data)
{
  gint fd;
  gint max_fd;

  g_return_val_if_fail (callback != NULL, FALSE);

  max_fd = sysconf (_SC_OPEN_MAX);
  for (fd = 0; fd < max_fd; fd++)
    {
      if (!callback (fd, user_data))
        return FALSE;
    }

  return TRUE;
}

/* ---------------------------------------------------------------------------------------------------- */

David Zeuthen's avatar
David Zeuthen committed
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
static gchar *
find_action_for_path (PolkitAuthority *authority,
                      const gchar     *path)
{
  GList *l;
  GList *actions;
  gchar *action_id;
  GError *error;

  actions = NULL;
  action_id = NULL;
  error = NULL;

  actions = polkit_authority_enumerate_actions_sync (authority,
                                                     NULL,
                                                     &error);
  if (actions == NULL)
    {
      g_warning ("Error enumerating actions: %s", error->message);
      g_error_free (error);
      goto out;
    }

  for (l = actions; l != NULL; l = l->next)
    {
      PolkitActionDescription *action_desc = POLKIT_ACTION_DESCRIPTION (l->data);
      const gchar *path_for_action;

      path_for_action = polkit_action_description_get_annotation (action_desc, "org.freedesktop.policykit.exec.path");
      if (path_for_action == NULL)
        continue;

      if (g_strcmp0 (path_for_action, path) == 0)
        {
          action_id = g_strdup (polkit_action_description_get_action_id (action_desc));
          goto out;
        }
    }

 out:
  g_list_foreach (actions, (GFunc) g_object_unref, NULL);
  g_list_free (actions);

  /* Fall back to org.freedesktop.policykit.exec */

  if (action_id == NULL)
    action_id = g_strdup ("org.freedesktop.policykit.exec");

  return action_id;
}

270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
/* ---------------------------------------------------------------------------------------------------- */

static gboolean
is_valid_shell (const gchar *shell)
{
  gboolean ret;
  gchar *contents;
  gchar **shells;
  GError *error;
  guint n;

  ret = FALSE;

  contents = NULL;
  shells = NULL;

  error = NULL;
  if (!g_file_get_contents ("/etc/shells",
                            &contents,
                            NULL, /* gsize *length */
                            &error))
    {
      g_printerr ("Error getting contents of /etc/shells: %s\n", error->message);
      g_error_free (error);
      goto out;
    }

  shells = g_strsplit (contents, "\n", 0);
  for (n = 0; shells != NULL && shells[n] != NULL; n++)
    {
      if (g_strcmp0 (shell, shells[n]) == 0)
        {
          ret = TRUE;
          goto out;
        }
    }

 out:
  g_free (contents);
  g_strfreev (shells);
  return ret;
}

static gboolean
validate_environment_variable (const gchar *key,
                               const gchar *value)
{
  gboolean ret;

  /* Generally we bail if any environment variable value contains
   *
   *   - '/' characters
   *   - '%' characters
   *   - '..' substrings
   */

  g_return_val_if_fail (key != NULL, FALSE);
  g_return_val_if_fail (value != NULL, FALSE);

  ret = FALSE;

  /* special case $SHELL */
  if (g_strcmp0 (key, "SHELL") == 0)
    {
      /* check if it's in /etc/shells */
      if (!is_valid_shell (value))
        {
337
          log_message (LOG_CRIT, TRUE,
338
                       "The value for the SHELL variable was not found the /etc/shells file");
339 340
          g_printerr ("\n"
                      "This incident has been reported.\n");
341 342 343 344 345 346 347
          goto out;
        }
    }
  else if (strstr (value, "/") != NULL ||
           strstr (value, "%") != NULL ||
           strstr (value, "..") != NULL)
    {
348
      log_message (LOG_CRIT, TRUE,
349
                   "The value for environment variable %s contains suscipious content",
350 351 352
                   key);
      g_printerr ("\n"
                  "This incident has been reported.\n");
353 354 355 356 357 358 359 360 361 362
      goto out;
    }

  ret = TRUE;

 out:
  return ret;
}

/* ---------------------------------------------------------------------------------------------------- */
David Zeuthen's avatar
David Zeuthen committed
363 364 365 366 367 368

int
main (int argc, char *argv[])
{
  guint n;
  guint ret;
369
  gint rc;
David Zeuthen's avatar
David Zeuthen committed
370 371 372 373 374
  gboolean opt_show_help;
  gboolean opt_show_version;
  PolkitAuthority *authority;
  PolkitAuthorizationResult *result;
  PolkitSubject *subject;
375
  PolkitDetails *details;
David Zeuthen's avatar
David Zeuthen committed
376 377 378 379 380 381 382 383
  GError *error;
  gchar *action_id;
  gchar **exec_argv;
  gchar *path;
  struct passwd pwstruct;
  gchar pwbuf[8192];
  gchar *s;
  const gchar *environment_variables_to_save[] = {
384
    "SHELL",
385
    "LANG",
386
    "LINGUAS",
David Zeuthen's avatar
David Zeuthen committed
387
    "LANGUAGE",
388 389
    "LC_COLLATE",
    "LC_CTYPE",
David Zeuthen's avatar
David Zeuthen committed
390
    "LC_MESSAGES",
391 392 393 394
    "LC_MONETARY",
    "LC_NUMERIC",
    "LC_TIME",
    "LC_ALL",
David Zeuthen's avatar
David Zeuthen committed
395
    "TERM",
396
    "COLORTERM",
David Zeuthen's avatar
David Zeuthen committed
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431

    /* For now, avoiding pretend that running X11 apps as another user in the same session
     * will ever work... See
     *
     *  https://bugs.freedesktop.org/show_bug.cgi?id=17970#c26
     *
     * and surrounding comments for a lot of discussion about this.
     */
#if 0
    "DESKTOP_STARTUP_ID",
    "DISPLAY",
    "XAUTHORITY",
    "DBUS_SESSION_BUS_ADDRESS",
    "ORBIT_SOCKETDIR",
#endif
    NULL
  };
  GPtrArray *saved_env;
  gchar *opt_user;
  pid_t pid_of_caller;

  ret = 127;
  authority = NULL;
  subject = NULL;
  details = NULL;
  result = NULL;
  action_id = NULL;
  saved_env = NULL;
  path = NULL;
  command_line = NULL;
  opt_user = NULL;

  /* check for correct invocation */
  if (geteuid () != 0)
    {
David Zeuthen's avatar
David Zeuthen committed
432
      g_printerr ("pkexec must be setuid root\n");
David Zeuthen's avatar
David Zeuthen committed
433 434 435
      goto out;
    }

436
  original_user_name = g_strdup (g_get_user_name ());
437 438
  if (original_user_name == NULL)
    {
David Zeuthen's avatar
David Zeuthen committed
439
      g_printerr ("Error getting user name.\n");
440 441 442 443 444 445
      goto out;
    }

  original_cwd = g_strdup (get_current_dir_name ());
  if (original_cwd == NULL)
    {
David Zeuthen's avatar
David Zeuthen committed
446
      g_printerr ("Error getting cwd.\n");
447 448
      goto out;
    }
449

David Zeuthen's avatar
David Zeuthen committed
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
  /* First process options and find the command-line to invoke. Avoid using fancy library routines
   * that depend on environtment variables since we haven't cleared the environment just yet.
   */
  opt_show_help = FALSE;
  opt_show_version = FALSE;
  for (n = 1; n < (guint) argc; n++)
    {
      if (strcmp (argv[n], "--help") == 0)
        {
          opt_show_help = TRUE;
        }
      else if (strcmp (argv[n], "--version") == 0)
        {
          opt_show_version = TRUE;
        }
      else if (strcmp (argv[n], "--user") == 0 || strcmp (argv[n], "-u") == 0)
        {
          n++;
          if (n >= (guint) argc)
            {
              usage (argc, argv);
              goto out;
            }

          opt_user = g_strdup (argv[n]);
        }
      else
        {
          break;
        }
    }

  if (opt_show_help)
    {
      usage (argc, argv);
      ret = 0;
      goto out;
    }
  else if (opt_show_version)
    {
      g_print ("pkexec version %s\n", PACKAGE_VERSION);
      ret = 0;
      goto out;
    }

  if (opt_user == NULL)
    opt_user = g_strdup ("root");

  /* Now figure out the command-line to run - argv is guaranteed to be NULL-terminated, see
   *
   *  http://lkml.indiana.edu/hypermail/linux/kernel/0409.2/0287.html
   *
   * but do check this is the case.
   *
   * We also try to locate the program in the path if a non-absolute path is given.
   */
  g_assert (argv[argc] == NULL);
  path = g_strdup (argv[n]);
  if (path == NULL)
    {
      usage (argc, argv);
      goto out;
    }
  if (path[0] != '/')
    {
      /* g_find_program_in_path() is not suspectible to attacks via the environment */
      s = g_find_program_in_path (path);
      if (s == NULL)
        {
          g_printerr ("Cannot run program %s: %s\n", path, strerror (ENOENT));
          goto out;
        }
      g_free (path);
      argv[n] = path = s;
    }
525
  if (access (path, F_OK) != 0)
David Zeuthen's avatar
David Zeuthen committed
526
    {
527
      g_printerr ("Error accessing %s: %s\n", path, g_strerror (errno));
David Zeuthen's avatar
David Zeuthen committed
528 529 530 531 532
      goto out;
    }
  command_line = g_strjoinv (" ", argv + n);
  exec_argv = argv + n;

533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
  /* Look up information about the user we care about - yes, the return
   * value of this function is a bit funky
   */
  rc = getpwnam_r (opt_user, &pwstruct, pwbuf, sizeof pwbuf, &pw);
  if (rc == 0 && pw == NULL)
    {
      g_printerr ("User `%s' does not exist.\n", opt_user);
      goto out;
    }
  else if (pw == NULL)
    {
      g_printerr ("Error getting information for user `%s': %s\n", opt_user, g_strerror (rc));
      goto out;
    }

David Zeuthen's avatar
David Zeuthen committed
548 549 550 551 552 553 554 555 556 557 558
  /* now save the environment variables we care about */
  saved_env = g_ptr_array_new ();
  for (n = 0; environment_variables_to_save[n] != NULL; n++)
    {
      const gchar *key = environment_variables_to_save[n];
      const gchar *value;

      value = g_getenv (key);
      if (value == NULL)
        continue;

559 560 561 562 563 564 565
      /* To qualify for the paranoia goldstar - we validate the value of each
       * environment variable passed through - this is to attempt to avoid
       * exploits in (potentially broken) programs launched via pkexec(1).
       */
      if (!validate_environment_variable (key, value))
        goto out;

David Zeuthen's avatar
David Zeuthen committed
566 567 568 569 570 571 572 573 574
      g_ptr_array_add (saved_env, g_strdup (key));
      g_ptr_array_add (saved_env, g_strdup (value));
    }

  /* Nuke the environment to get a well-known and sanitized environment to avoid attacks
   * via e.g. the DBUS_SYSTEM_BUS_ADDRESS environment variable and similar.
   */
  if (clearenv () != 0)
    {
575
      g_printerr ("Error clearing environment: %s\n", g_strerror (errno));
David Zeuthen's avatar
David Zeuthen committed
576 577 578 579 580 581 582 583
      goto out;
    }

  /* Initialize the GLib type system - this is needed to interact with the
   * PolicyKit daemon
   */
  g_type_init ();

584 585 586 587 588 589 590 591 592 593 594 595
  /* make sure we are nuked if the parent process dies */
#ifdef __linux__
  if (prctl (PR_SET_PDEATHSIG, SIGTERM) != 0)
    {
      g_printerr ("prctl(PR_SET_PDEATHSIG, SIGTERM) failed: %s\n", g_strerror (errno));
      goto out;
    }
#else
#warning "Please add OS specific code to catch when the parent dies"
#endif

  /* Figure out the parent process */
David Zeuthen's avatar
David Zeuthen committed
596 597 598 599
  pid_of_caller = getppid ();
  if (pid_of_caller == 1)
    {
      /* getppid() can return 1 if the parent died (meaning that we are reaped
600
       * by /sbin/init); In that case we simpy bail.
David Zeuthen's avatar
David Zeuthen committed
601
       */
602
      g_printerr ("Refusing to render service to dead parents.\n");
603 604 605
      goto out;
    }

606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626
  /* This process we want to check an authorization for is the process
   * that launched us - our parent process.
   *
   * At the time the parent process fork()'ed and exec()'ed us, the
   * process had the same real-uid that we have now. So we use this
   * real-uid instead of of looking it up to avoid TOCTTOU issues
   * (consider the parent process exec()'ing a setuid helper).
   *
   * On the other hand, the monotonic process start-time is guaranteed
   * to never change so it's safe to look that up given only the PID
   * since we are guaranteed to be nuked if the parent goes away
   * (cf. the prctl(2) call above).
   */
  subject = polkit_unix_process_new_for_owner (pid_of_caller,
                                               0, /* 0 means "look up start-time in /proc" */
                                               getuid ());
  /* really double-check the invariants guaranteed by the PolkitUnixProcess class */
  g_assert (subject != NULL);
  g_assert (polkit_unix_process_get_pid (POLKIT_UNIX_PROCESS (subject)) == pid_of_caller);
  g_assert (polkit_unix_process_get_uid (POLKIT_UNIX_PROCESS (subject)) >= 0);
  g_assert (polkit_unix_process_get_start_time (POLKIT_UNIX_PROCESS (subject)) > 0);
David Zeuthen's avatar
David Zeuthen committed
627 628 629

  authority = polkit_authority_get ();

630
  details = polkit_details_new ();
David Zeuthen's avatar
David Zeuthen committed
631

632
  polkit_details_insert (details, "command-line", command_line);
David Zeuthen's avatar
David Zeuthen committed
633
  s = g_strdup_printf ("%s (%s)", pw->pw_gecos, pw->pw_name);
634 635
  polkit_details_insert (details, "user", s);
  g_free (s);
David Zeuthen's avatar
David Zeuthen committed
636
  s = g_strdup_printf ("%d", (gint) pw->pw_uid);
637 638 639
  polkit_details_insert (details, "uid", s);
  g_free (s);
  polkit_details_insert (details, "program", path);
David Zeuthen's avatar
David Zeuthen committed
640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665

  action_id = find_action_for_path (authority, path);
  g_assert (action_id != NULL);

  error = NULL;
  result = polkit_authority_check_authorization_sync (authority,
                                                      subject,
                                                      action_id,
                                                      details,
                                                      POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION,
                                                      NULL,
                                                      &error);
  if (result == NULL)
    {
      g_printerr ("Error checking for authorization %s: %s\n",
                  action_id,
                  error->message);
      goto out;
    }

  if (polkit_authorization_result_get_is_authorized (result))
    {
      /* do nothing */
    }
  else if (polkit_authorization_result_get_is_challenge (result))
    {
666
      g_printerr ("Error executing command as another user: No authentication agent was found.\n");
David Zeuthen's avatar
David Zeuthen committed
667 668 669 670
      goto out;
    }
  else
    {
671 672 673 674
      log_message (LOG_WARNING, TRUE,
                   "Error executing command as another user: Not authorized");
      g_printerr ("\n"
                  "This incident has been reported.\n");
David Zeuthen's avatar
David Zeuthen committed
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703
      goto out;
    }

  /* Set PATH to a safe list */
  g_ptr_array_add (saved_env, g_strdup ("PATH"));
  if (pw->pw_uid != 0)
    s = g_strdup_printf ("/usr/bin:/bin:/usr/sbin:/sbin:%s/bin", pw->pw_dir);
  else
    s = g_strdup_printf ("/usr/sbin:/usr/bin:/sbin:/bin:%s/bin", pw->pw_dir);
  g_ptr_array_add (saved_env, s);
  g_ptr_array_add (saved_env, g_strdup ("LOGNAME"));
  g_ptr_array_add (saved_env, g_strdup (pw->pw_name));
  g_ptr_array_add (saved_env, g_strdup ("USER"));
  g_ptr_array_add (saved_env, g_strdup (pw->pw_name));
  g_ptr_array_add (saved_env, g_strdup ("HOME"));
  g_ptr_array_add (saved_env, g_strdup (pw->pw_dir));

  s = g_strdup_printf ("%d", getuid ());
  g_ptr_array_add (saved_env, g_strdup ("PKEXEC_UID"));
  g_ptr_array_add (saved_env, s);

  /* set the environment */
  for (n = 0; n < saved_env->len - 1; n += 2)
    {
      const gchar *key = saved_env->pdata[n];
      const gchar *value = saved_env->pdata[n + 1];

      if (!g_setenv (key, value, TRUE))
        {
704
          g_printerr ("Error setting environment variable %s to '%s': %s\n",
David Zeuthen's avatar
David Zeuthen committed
705
                      key,
706 707
                      value,
                      g_strerror (errno));
David Zeuthen's avatar
David Zeuthen committed
708 709 710 711
          goto out;
        }
    }

712 713 714 715 716 717 718
  /* set close_on_exec on all file descriptors except stdin, stdout, stderr */
  if (!fdwalk (set_close_on_exec, GINT_TO_POINTER (3)))
    {
      g_printerr ("Error setting close-on-exec for file desriptors\n");
      goto out;
    }

David Zeuthen's avatar
David Zeuthen committed
719
  /* if not changing to uid 0, become uid 0 before changing to the user */
720
  if (pw->pw_uid != 0)
David Zeuthen's avatar
David Zeuthen committed
721 722 723 724
    {
      setreuid (0, 0);
      if ((geteuid () != 0) || (getuid () != 0))
        {
725
          g_printerr ("Error becoming uid 0: %s\n", g_strerror (errno));
David Zeuthen's avatar
David Zeuthen committed
726 727 728 729
          goto out;
        }
    }

730 731 732 733 734 735 736 737 738 739 740 741
  /* open session - with PAM enabled, this runs the open_session() part of the PAM
   * stack - this includes applying limits via pam_limits.so but also other things
   * requested via the current PAM configuration.
   *
   * NOTE NOTE NOTE: pam_limits.so doesn't seem to clear existing limits - e.g.
   *
   *  $ ulimit -t
   *  unlimited
   *
   *  $ su -
   *  Password:
   *  # ulimit -t
David Zeuthen's avatar
David Zeuthen committed
742
   *  unlimited
743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760
   *  # logout
   *
   *  $ ulimit -t 1000
   *  $ ulimit -t
   *  1000
   *  $ su -
   *  Password:
   *  # ulimit -t
   *  1000
   *
   * TODO: The question here is whether we should clear the limits before applying them?
   * As evident above, neither su(1) (and, for that matter, nor sudo(8)) does this.
   */
  if (!open_session (pw->pw_name))
    {
      goto out;
    }

David Zeuthen's avatar
David Zeuthen committed
761 762 763
  /* become the user */
  if (setgroups (0, NULL) != 0)
    {
764
      g_printerr ("Error setting groups: %s\n", g_strerror (errno));
David Zeuthen's avatar
David Zeuthen committed
765 766 767 768
      goto out;
    }
  if (initgroups (pw->pw_name, pw->pw_gid) != 0)
    {
769
      g_printerr ("Error initializing groups for %s: %s\n", pw->pw_name, g_strerror (errno));
David Zeuthen's avatar
David Zeuthen committed
770 771 772 773 774 775 776
      goto out;
    }
  setregid (pw->pw_gid, pw->pw_gid);
  setreuid (pw->pw_uid, pw->pw_uid);
  if ((geteuid () != pw->pw_uid) || (getuid () != pw->pw_uid) ||
      (getegid () != pw->pw_gid) || (getgid () != pw->pw_gid))
    {
777
      g_printerr ("Error becoming real+effective uid %d and gid %d: %s\n", pw->pw_uid, pw->pw_gid, g_strerror (errno));
David Zeuthen's avatar
David Zeuthen committed
778 779 780 781 782 783
      goto out;
    }

  /* change to home directory */
  if (chdir (pw->pw_dir) != 0)
    {
784
      g_printerr ("Error changing to home directory %s: %s\n", pw->pw_dir, g_strerror (errno));
David Zeuthen's avatar
David Zeuthen committed
785 786 787
      goto out;
    }

788 789
  /* Log the fact that we're executing a command */
  log_message (LOG_NOTICE, FALSE, "Executing command");
790

David Zeuthen's avatar
David Zeuthen committed
791 792 793
  /* exec the program */
  if (execv (path, exec_argv) != 0)
    {
794
      g_printerr ("Error executing %s: %s\n", path, g_strerror (errno));
David Zeuthen's avatar
David Zeuthen committed
795 796 797 798 799 800 801 802 803 804 805 806 807
      goto out;
    }

  /* if exec doesn't fail, it never returns... */
  g_assert_not_reached ();

 out:
  if (result != NULL)
    g_object_unref (result);

  g_free (action_id);

  if (details != NULL)
808
    g_object_unref (details);
David Zeuthen's avatar
David Zeuthen committed
809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824

  if (subject != NULL)
    g_object_unref (subject);

  if (authority != NULL)
    g_object_unref (authority);

  if (saved_env != NULL)
    {
      g_ptr_array_foreach (saved_env, (GFunc) g_free, NULL);
      g_ptr_array_free (saved_env, TRUE);
    }

  g_free (path);
  g_free (command_line);
  g_free (opt_user);
825
  g_free (original_user_name);
826
  g_free (original_cwd);
David Zeuthen's avatar
David Zeuthen committed
827 828 829 830

  return ret;
}