pkexec.c 29.6 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
26
27
28
29
30
/*
 * 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

#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
31
#include <fcntl.h>
David Zeuthen's avatar
David Zeuthen committed
32
33
34
#include <grp.h>
#include <pwd.h>
#include <errno.h>
Andrew Psaltis's avatar
Andrew Psaltis committed
35

36
37
38
39
#ifdef __linux__
#include <sys/prctl.h>
#endif

40
41
#include <glib/gi18n.h>

Andrew Psaltis's avatar
Andrew Psaltis committed
42
#ifdef POLKIT_AUTHFW_PAM
43
#include <security/pam_appl.h>
Andrew Psaltis's avatar
Andrew Psaltis committed
44
45
#endif /* POLKIT_AUTHFW_PAM */

46
47
#include <syslog.h>
#include <stdarg.h>
David Zeuthen's avatar
David Zeuthen committed
48
49

#include <polkit/polkit.h>
50
51
#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE
#include <polkitagent/polkitagent.h>
David Zeuthen's avatar
David Zeuthen committed
52

53
static gchar *original_user_name = NULL;
Emilio Pozuelo Monfort's avatar
Emilio Pozuelo Monfort committed
54
static gchar *original_cwd;
55
56
57
static gchar *command_line = NULL;
static struct passwd *pw;

58
59
60
61
62
63
64
65
66
67
68
69
#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
70
71
72
static void
usage (int argc, char *argv[])
{
73
74
  g_printerr ("pkexec --version |\n"
              "       --help |\n"
75
              "       --disable-internal-agent |\n"
Adrian Vovk's avatar
Adrian Vovk committed
76
              "       [--keep-cwd] [--user username] [PROGRAM] [ARGUMENTS...]\n"
77
              "\n"
78
79
80
81
82
              "See the pkexec manual page for more details.\n"
	      "\n"
	      "Report bugs to: %s\n"
	      "%s home page: <%s>\n", PACKAGE_BUGREPORT, PACKAGE_NAME,
	      PACKAGE_URL);
David Zeuthen's avatar
David Zeuthen committed
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
110
111
112
113
114
115
116
117
118
119
120
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,
121
          original_cwd,
122
123
124
125
126
127
128
129
130
131
132
          command_line);

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

  g_free (s);
}

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

Andrew Psaltis's avatar
Andrew Psaltis committed
133
#ifdef POLKIT_AUTHFW_PAM
134
135
136
137
138
139
140
141
142
143
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;
}

144
145
146
147
148
149
150
151
152
153
154
155
156
/* A work around for:
 * https://bugzilla.redhat.com/show_bug.cgi?id=753882
 */
static gboolean
xdg_runtime_dir_is_owned_by (const char *path,
			     uid_t       target_uid)
{
  struct stat stbuf;

  return stat (path, &stbuf) == 0 &&
    stbuf.st_uid == target_uid;
}

157
static gboolean
158
159
open_session (const gchar *user_to_auth,
	      uid_t        target_uid)
160
161
162
163
{
  gboolean ret;
  gint rc;
  pam_handle_t *pam_h;
164
  char **envlist;
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
  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;

196
197
198
199
200
  envlist = pam_getenvlist (pam_h);
  if (envlist != NULL)
    {
      guint n;
      for (n = 0; envlist[n]; n++)
201
202
203
204
205
206
207
208
209
210
211
212
213
	{
	  const char *envitem = envlist[n];
	  
	  if (g_str_has_prefix (envitem, "XDG_RUNTIME_DIR="))
	    {
	      const char *eq = strchr (envitem, '=');
	      g_assert (eq);
	      if (!xdg_runtime_dir_is_owned_by (eq + 1, target_uid))
		continue;
	    }

	  putenv (envlist[n]);
	}
214
215
216
      free (envlist);
    }

217
218
219
220
221
out:
  if (pam_h != NULL)
    pam_end (pam_h, rc);
  return ret;
}
Andrew Psaltis's avatar
Andrew Psaltis committed
222
#endif /* POLKIT_AUTHFW_PAM */
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
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
268
269
static gchar *
find_action_for_path (PolkitAuthority *authority,
270
                      const gchar     *path,
271
                      const gchar     *argv1,
272
                      gboolean        *allow_gui)
David Zeuthen's avatar
David Zeuthen committed
273
274
275
276
277
278
279
280
281
{
  GList *l;
  GList *actions;
  gchar *action_id;
  GError *error;

  actions = NULL;
  action_id = NULL;
  error = NULL;
282
  *allow_gui = FALSE;
David Zeuthen's avatar
David Zeuthen committed
283
284
285
286
287
288
289
290
291
292
293
294
295
296

  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);
297
      const gchar *argv1_for_action;
David Zeuthen's avatar
David Zeuthen committed
298
      const gchar *path_for_action;
299
      const gchar *allow_gui_annotation;
David Zeuthen's avatar
David Zeuthen committed
300
301
302
303
304

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

305
306
      argv1_for_action = polkit_action_description_get_annotation (action_desc, "org.freedesktop.policykit.exec.argv1");

David Zeuthen's avatar
David Zeuthen committed
307
308
      if (g_strcmp0 (path_for_action, path) == 0)
        {
309
310
311
312
313
314
315
          /* check against org.freedesktop.policykit.exec.argv1 but only if set */
          if (argv1_for_action != NULL)
            {
              if (g_strcmp0 (argv1, argv1_for_action) != 0)
                continue;
            }

David Zeuthen's avatar
David Zeuthen committed
316
          action_id = g_strdup (polkit_action_description_get_action_id (action_desc));
317
318
319
320
321
322

          allow_gui_annotation = polkit_action_description_get_annotation (action_desc, "org.freedesktop.policykit.exec.allow_gui");

          if (allow_gui_annotation != NULL && strlen (allow_gui_annotation) > 0)
            *allow_gui = TRUE;

David Zeuthen's avatar
David Zeuthen committed
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
          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;
}

339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
/* ---------------------------------------------------------------------------------------------------- */

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))
        {
406
          log_message (LOG_CRIT, TRUE,
407
                       "The value for the SHELL variable was not found in the /etc/shells file");
408
409
          g_printerr ("\n"
                      "This incident has been reported.\n");
410
411
412
          goto out;
        }
    }
413
  else if ((g_strcmp0 (key, "XAUTHORITY") != 0 && strstr (value, "/") != NULL) ||
414
415
416
           strstr (value, "%") != NULL ||
           strstr (value, "..") != NULL)
    {
417
      log_message (LOG_CRIT, TRUE,
418
                   "The value for environment variable %s contains suspicious content",
419
420
421
                   key);
      g_printerr ("\n"
                  "This incident has been reported.\n");
422
423
424
425
426
427
428
429
430
      goto out;
    }

  ret = TRUE;

 out:
  return ret;
}

431

432
/* ---------------------------------------------------------------------------------------------------- */
David Zeuthen's avatar
David Zeuthen committed
433
434
435
436
437
438

int
main (int argc, char *argv[])
{
  guint n;
  guint ret;
439
  gint rc;
David Zeuthen's avatar
David Zeuthen committed
440
441
  gboolean opt_show_help;
  gboolean opt_show_version;
442
  gboolean opt_disable_internal_agent;
Adrian Vovk's avatar
Adrian Vovk committed
443
  gboolean opt_keep_cwd;
David Zeuthen's avatar
David Zeuthen committed
444
445
446
  PolkitAuthority *authority;
  PolkitAuthorizationResult *result;
  PolkitSubject *subject;
447
  PolkitDetails *details;
David Zeuthen's avatar
David Zeuthen committed
448
449
  GError *error;
  gchar *action_id;
450
  gboolean allow_gui;
David Zeuthen's avatar
David Zeuthen committed
451
452
453
454
455
456
  gchar **exec_argv;
  gchar *path;
  struct passwd pwstruct;
  gchar pwbuf[8192];
  gchar *s;
  const gchar *environment_variables_to_save[] = {
457
    "SHELL",
458
    "LANG",
459
    "LINGUAS",
David Zeuthen's avatar
David Zeuthen committed
460
    "LANGUAGE",
461
462
    "LC_COLLATE",
    "LC_CTYPE",
David Zeuthen's avatar
David Zeuthen committed
463
    "LC_MESSAGES",
464
465
466
467
    "LC_MONETARY",
    "LC_NUMERIC",
    "LC_TIME",
    "LC_ALL",
David Zeuthen's avatar
David Zeuthen committed
468
    "TERM",
469
    "COLORTERM",
David Zeuthen's avatar
David Zeuthen committed
470

471
472
    /* By default we don't allow running X11 apps, as it does not work in the
     * general case. See
David Zeuthen's avatar
David Zeuthen committed
473
474
475
476
     *
     *  https://bugs.freedesktop.org/show_bug.cgi?id=17970#c26
     *
     * and surrounding comments for a lot of discussion about this.
477
478
479
480
481
     *
     * However, it can be enabled for some selected and tested legacy programs
     * which previously used e. g. gksu, by setting the
     * org.freedesktop.policykit.exec.allow_gui annotation to a nonempty value.
     * See https://bugs.freedesktop.org/show_bug.cgi?id=38769 for details.
David Zeuthen's avatar
David Zeuthen committed
482
483
484
485
486
487
488
489
     */
    "DISPLAY",
    "XAUTHORITY",
    NULL
  };
  GPtrArray *saved_env;
  gchar *opt_user;
  pid_t pid_of_caller;
490
  gpointer local_agent_handle;
David Zeuthen's avatar
David Zeuthen committed
491

492
493
494
495
496
497
498
499
500

  /*
   * If 'pkexec' is called THIS wrong, someone's probably evil-doing. Don't be nice, just bail out.
   */
  if (argc<1)
    {
      exit(127);
    }

David Zeuthen's avatar
David Zeuthen committed
501
502
503
504
505
506
507
508
  ret = 127;
  authority = NULL;
  subject = NULL;
  details = NULL;
  result = NULL;
  action_id = NULL;
  saved_env = NULL;
  path = NULL;
509
  exec_argv = NULL;
David Zeuthen's avatar
David Zeuthen committed
510
511
  command_line = NULL;
  opt_user = NULL;
512
  local_agent_handle = NULL;
David Zeuthen's avatar
David Zeuthen committed
513

514
515
516
  /* Disable remote file access from GIO. */
  setenv ("GIO_USE_VFS", "local", 1);

David Zeuthen's avatar
David Zeuthen committed
517
518
519
520
521
  /* 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;
522
  opt_disable_internal_agent = FALSE;
Adrian Vovk's avatar
Adrian Vovk committed
523
  opt_keep_cwd = FALSE;
David Zeuthen's avatar
David Zeuthen committed
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
  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;
            }

543
544
545
546
547
          if (opt_user != NULL)
            {
              g_printerr ("--user specified twice\n");
              goto out;
            }
David Zeuthen's avatar
David Zeuthen committed
548
549
          opt_user = g_strdup (argv[n]);
        }
550
551
552
553
      else if (strcmp (argv[n], "--disable-internal-agent") == 0)
        {
          opt_disable_internal_agent = TRUE;
        }
Adrian Vovk's avatar
Adrian Vovk committed
554
555
556
557
      else if (strcmp (argv[n], "--keep-cwd") == 0)
        {
          opt_keep_cwd = TRUE;
        }
David Zeuthen's avatar
David Zeuthen committed
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
      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;
    }

577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
  /* check for correct invocation */
  if (geteuid () != 0)
    {
      g_printerr ("pkexec must be setuid root\n");
      goto out;
    }

  original_user_name = g_strdup (g_get_user_name ());
  if (original_user_name == NULL)
    {
      g_printerr ("Error getting user name.\n");
      goto out;
    }

  if ((original_cwd = g_get_current_dir ()) == NULL)
    {
      g_printerr ("Error getting cwd: %s\n",
                  g_strerror (errno));
      goto out;
    }

David Zeuthen's avatar
David Zeuthen committed
598
599
600
  if (opt_user == NULL)
    opt_user = g_strdup ("root");

601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
  /* 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
616
617
618
619
620
621
622
623
624
625
626
627
  /* 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)
    {
628
629
630
631
      GPtrArray *shell_argv;

      path = g_strdup (pwstruct.pw_shell);
      if (!path)
632
        {
633
634
          g_printerr ("No shell configured or error retrieving pw_shell\n");
          goto out;
635
        }
636
637
638
639
640
641
642
      /* If you change this, be sure to change the if (!command_line)
	 case below too */
      command_line = g_strdup (path);
      shell_argv = g_ptr_array_new ();
      g_ptr_array_add (shell_argv, path);
      g_ptr_array_add (shell_argv, NULL);
      exec_argv = (char**)g_ptr_array_free (shell_argv, FALSE);
David Zeuthen's avatar
David Zeuthen committed
643
644
645
646
647
648
649
650
651
652
653
    }
  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);
654
655
656
657
658
659
660
661
662
      path = s;

      /* argc<2 and pkexec runs just shell, argv is guaranteed to be null-terminated.
       * /-less shell shouldn't happen, but let's be defensive and don't write to null-termination
       */
      if (argv[n] != NULL)
      {
        argv[n] = path;
      }
David Zeuthen's avatar
David Zeuthen committed
663
    }
664
  if (access (path, F_OK) != 0)
David Zeuthen's avatar
David Zeuthen committed
665
    {
666
      g_printerr ("Error accessing %s: %s\n", path, g_strerror (errno));
David Zeuthen's avatar
David Zeuthen committed
667
668
669
      goto out;
    }

670
  if (!command_line)
671
    {
672
673
674
675
      /* If you change this, be sure to change the path == NULL case
	 above too */
      command_line = g_strjoinv (" ", argv + n);
      exec_argv = argv + n;
676
677
    }

David Zeuthen's avatar
David Zeuthen committed
678
679
680
681
682
683
684
685
686
687
688
  /* 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;

689
690
691
692
693
694
695
      /* 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
696
697
698
699
      g_ptr_array_add (saved_env, g_strdup (key));
      g_ptr_array_add (saved_env, g_strdup (value));
    }

700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
  /* $XAUTHORITY is "special" - if unset, we need to set it to ~/.Xauthority. Yes,
   * this is broken but it's unfortunately how things work (see fdo #51623 for
   * details)
   */
  if (g_getenv ("XAUTHORITY") == NULL)
    {
      const gchar *home;

      /* pre-2.36 GLib does not examine $HOME (it always looks in /etc/passwd) and
       * this is not what we want
       */
      home = g_getenv ("HOME");
      if (home == NULL)
        home = g_get_home_dir ();

      if (home != NULL)
        {
          g_ptr_array_add (saved_env, g_strdup ("XAUTHORITY"));
          g_ptr_array_add (saved_env, g_build_filename (home, ".Xauthority", NULL));
        }
    }

David Zeuthen's avatar
David Zeuthen committed
722
723
724
725
726
  /* 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)
    {
727
      g_printerr ("Error clearing environment: %s\n", g_strerror (errno));
David Zeuthen's avatar
David Zeuthen committed
728
729
730
      goto out;
    }

731
732
733
734
735
736
737
738
739
740
741
742
  /* 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
743
744
745
746
  pid_of_caller = getppid ();
  if (pid_of_caller == 1)
    {
      /* getppid() can return 1 if the parent died (meaning that we are reaped
747
       * by /sbin/init); In that case we simpy bail.
David Zeuthen's avatar
David Zeuthen committed
748
       */
749
      g_printerr ("Refusing to render service to dead parents.\n");
750
751
752
      goto out;
    }

753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
  /* 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
774

775
776
777
778
779
780
781
782
  error = NULL;
  authority = polkit_authority_get_sync (NULL /* GCancellable* */, &error);
  if (authority == NULL)
    {
      g_printerr ("Error getting authority: %s\n", error->message);
      g_error_free (error);
      goto out;
    }
David Zeuthen's avatar
David Zeuthen committed
783

784
785
  g_assert (path != NULL);
  g_assert (exec_argv != NULL);
786
787
788
789
  action_id = find_action_for_path (authority,
                                    path,
                                    exec_argv[1],
                                    &allow_gui);
790
  g_assert (action_id != NULL);
David Zeuthen's avatar
David Zeuthen committed
791

792
  details = polkit_details_new ();
David Zeuthen's avatar
David Zeuthen committed
793
  polkit_details_insert (details, "user", pw->pw_name);
David Zeuthen's avatar
David Zeuthen committed
794
795
  if (pw->pw_gecos != NULL)
    polkit_details_insert (details, "user.gecos", pw->pw_gecos);
796
797
798
799
  if (pw->pw_gecos != NULL && strlen (pw->pw_gecos) > 0)
    s = g_strdup_printf ("%s (%s)", pw->pw_gecos, pw->pw_name);
  else
    s = g_strdup_printf ("%s", pw->pw_name);
David Zeuthen's avatar
David Zeuthen committed
800
  polkit_details_insert (details, "user.display", s);
801
802
  g_free (s);
  polkit_details_insert (details, "program", path);
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
  polkit_details_insert (details, "command_line", command_line);
  if (g_strcmp0 (action_id, "org.freedesktop.policykit.exec") == 0)
    {
      if (pw->pw_uid == 0)
        {
          polkit_details_insert (details, "polkit.message",
                                 /* Translators: message shown when trying to run a program as root. Do not
                                  * translate the $(program) fragment - it will be expanded to the path
                                  * of the program e.g.  /bin/bash.
                                  */
                                 N_("Authentication is needed to run `$(program)' as the super user"));
        }
      else
        {
          polkit_details_insert (details, "polkit.message",
                                 /* Translators: message shown when trying to run a program as another user.
                                  * Do not translate the $(program) or $(user) fragments - the former will
                                  * be expanded to the path of the program e.g. "/bin/bash" and the latter
                                  * to the user e.g. "John Doe (johndoe)" or "johndoe".
                                  */
David Zeuthen's avatar
David Zeuthen committed
823
                                 N_("Authentication is needed to run `$(program)' as user $(user.display)"));
824
825
826
        }
    }
  polkit_details_insert (details, "polkit.gettext_domain", GETTEXT_PACKAGE);
David Zeuthen's avatar
David Zeuthen committed
827

828
 try_again:
David Zeuthen's avatar
David Zeuthen committed
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
  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))
    {
851
      if (local_agent_handle == NULL && !opt_disable_internal_agent)
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
        {
          PolkitAgentListener *listener;
          error = NULL;
          /* this will fail if we can't find a controlling terminal */
          listener = polkit_agent_text_listener_new (NULL, &error);
          if (listener == NULL)
            {
              g_printerr ("Error creating textual authentication agent: %s\n", error->message);
              g_error_free (error);
              goto out;
            }
          local_agent_handle = polkit_agent_listener_register (listener,
                                                               POLKIT_AGENT_REGISTER_FLAGS_RUN_IN_THREAD,
                                                               subject,
                                                               NULL, /* object_path */
                                                               NULL, /* GCancellable */
                                                               &error);
          g_object_unref (listener);
          if (local_agent_handle == NULL)
            {
              g_printerr ("Error registering local authentication agent: %s\n", error->message);
              g_error_free (error);
              goto out;
            }
          g_object_unref (result);
          result = NULL;
          goto try_again;
        }
      else
        {
882
          g_printerr ("Error executing command as another user: No authentication agent found.\n");
883
884
          goto out;
        }
David Zeuthen's avatar
David Zeuthen committed
885
886
887
    }
  else
    {
888
889
890
891
892
893
894
895
896
897
898
899
900
      if (polkit_authorization_result_get_dismissed (result))
        {
          log_message (LOG_WARNING, TRUE,
                       "Error executing command as another user: Request dismissed");
          ret = 126;
        }
      else
        {
          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
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
      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];

928
929
930
931
932
      /* Only set $DISPLAY and $XAUTHORITY when explicitly allowed in the .policy */
      if (!allow_gui &&
              (strcmp (key, "DISPLAY") == 0 || strcmp (key, "XAUTHORITY") == 0))
          continue;

David Zeuthen's avatar
David Zeuthen committed
933
934
      if (!g_setenv (key, value, TRUE))
        {
935
          g_printerr ("Error setting environment variable %s to '%s': %s\n",
David Zeuthen's avatar
David Zeuthen committed
936
                      key,
937
938
                      value,
                      g_strerror (errno));
David Zeuthen's avatar
David Zeuthen committed
939
940
941
942
          goto out;
        }
    }

943
944
945
946
947
948
949
  /* 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
950
  /* if not changing to uid 0, become uid 0 before changing to the user */
951
  if (pw->pw_uid != 0)
David Zeuthen's avatar
David Zeuthen committed
952
953
954
955
    {
      setreuid (0, 0);
      if ((geteuid () != 0) || (getuid () != 0))
        {
956
          g_printerr ("Error becoming uid 0: %s\n", g_strerror (errno));
David Zeuthen's avatar
David Zeuthen committed
957
958
959
960
          goto out;
        }
    }

961
962
963
964
965
966
967
968
969
970
971
972
  /* 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
973
   *  unlimited
974
975
976
977
978
979
980
981
982
983
984
985
986
   *  # 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.
   */
Andrew Psaltis's avatar
Andrew Psaltis committed
987
#ifdef POLKIT_AUTHFW_PAM
988
989
  if (!open_session (pw->pw_name,
		     pw->pw_uid))
990
991
992
    {
      goto out;
    }
Andrew Psaltis's avatar
Andrew Psaltis committed
993
#endif /* POLKIT_AUTHFW_PAM */
994

David Zeuthen's avatar
David Zeuthen committed
995
996
997
  /* become the user */
  if (setgroups (0, NULL) != 0)
    {
998
      g_printerr ("Error setting groups: %s\n", g_strerror (errno));
David Zeuthen's avatar
David Zeuthen committed
999
1000
      goto out;
    }