pkttyagent.c 9.03 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
/*
 * Copyright (C) 2009-2012 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 <stdio.h>
27
#include <stdlib.h>
28 29
#include <signal.h>
#include <termios.h>
Miloslav Trmač's avatar
Miloslav Trmač committed
30
#include <glib/gi18n.h>
David Zeuthen's avatar
David Zeuthen committed
31 32 33 34
#include <polkit/polkit.h>
#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE
#include <polkitagent/polkitagent.h>

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58

static volatile sig_atomic_t tty_flags_saved;
struct termios ts;
FILE *tty = NULL;
struct sigaction savesigterm, savesigint, savesigtstp;


static void tty_handler(int signal)
{
  switch (signal)
  {
    case SIGTERM:
      sigaction (SIGTERM, &savesigterm, NULL);
      break;
    case SIGINT:
      sigaction (SIGINT, &savesigint, NULL);
      break;
    case SIGTSTP:
      sigaction (SIGTSTP, &savesigtstp, NULL);
      break;
  }

  if (tty_flags_saved)
  {
59
    tcsetattr (fileno (tty), TCSADRAIN, &ts);
60 61 62 63 64 65
  }

  kill(getpid(), signal);
}


David Zeuthen's avatar
David Zeuthen committed
66 67 68 69
int
main (int argc, char *argv[])
{
  gboolean opt_show_version = FALSE;
70
  gboolean opt_fallback = FALSE;
Miloslav Trmač's avatar
Miloslav Trmač committed
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
  gchar *opt_process = NULL;
  gchar *opt_system_bus_name = NULL;
  gint opt_notify_fd = -1;
  GOptionEntry options[] =
    {
      {
	"fallback", 0, 0, G_OPTION_ARG_NONE, &opt_fallback,
	N_("Don't replace existing agent if any"), NULL
      },
      {
	"notify-fd", 0, 0, G_OPTION_ARG_INT, &opt_notify_fd,
	N_("Close FD when the agent is registered"), N_("FD")
      },
      {
	"process", 'p', 0, G_OPTION_ARG_STRING, &opt_process,
	N_("Register the agent for the specified process"),
	N_("PID[,START_TIME]")
      },
      {
	"system-bus-name", 's', 0, G_OPTION_ARG_STRING, &opt_system_bus_name,
Miloslav Trmač's avatar
Miloslav Trmač committed
91
	N_("Register the agent for the owner of BUS_NAME"), N_("BUS_NAME")
Miloslav Trmač's avatar
Miloslav Trmač committed
92 93 94 95 96 97 98 99
      },
      {
	"version", 0, 0, G_OPTION_ARG_NONE, &opt_show_version,
	N_("Show version"), NULL
      },
      { NULL, 0, 0, 0, NULL, NULL, NULL }
    };
  GOptionContext *context;
100
  gchar *s;
David Zeuthen's avatar
David Zeuthen committed
101 102 103 104
  PolkitAuthority *authority = NULL;
  PolkitSubject *subject = NULL;
  gpointer local_agent_handle = NULL;
  PolkitAgentListener *listener = NULL;
Miloslav Trmač's avatar
Miloslav Trmač committed
105
  GVariant *listener_options = NULL;
David Zeuthen's avatar
David Zeuthen committed
106 107 108
  GError *error;
  GMainLoop *loop = NULL;
  guint ret = 126;
109
  GVariantBuilder builder;
110 111
  struct sigaction sa;
  const char *tty_name = NULL;
David Zeuthen's avatar
David Zeuthen committed
112

113 114 115
  /* Disable remote file access from GIO. */
  setenv ("GIO_USE_VFS", "local", 1);

Miloslav Trmač's avatar
Miloslav Trmač committed
116 117
  error = NULL;
  context = g_option_context_new ("");
118 119 120 121 122
  s = g_strdup_printf (_("Report bugs to: %s\n"
			 "%s home page: <%s>"), PACKAGE_BUGREPORT,
		       PACKAGE_NAME, PACKAGE_URL);
  g_option_context_set_description (context, s);
  g_free (s);
Miloslav Trmač's avatar
Miloslav Trmač committed
123 124
  g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE);
  if (!g_option_context_parse (context, &argc, &argv, &error))
David Zeuthen's avatar
David Zeuthen committed
125
    {
Miloslav Trmač's avatar
Miloslav Trmač committed
126 127
      g_printerr ("%s: %s\n", g_get_prgname (), error->message);
      g_error_free (error);
David Zeuthen's avatar
David Zeuthen committed
128 129
      goto out;
    }
130 131 132 133 134 135
  if (argc > 1)
    {
      g_printerr (_("%s: Unexpected argument `%s'\n"), g_get_prgname (),
		  argv[1]);
      goto out;
    }
Miloslav Trmač's avatar
Miloslav Trmač committed
136 137

  if (opt_show_version)
David Zeuthen's avatar
David Zeuthen committed
138 139 140 141 142 143
    {
      g_print ("pkttyagent version %s\n", PACKAGE_VERSION);
      ret = 0;
      goto out;
    }

Miloslav Trmač's avatar
Miloslav Trmač committed
144 145 146 147 148 149 150
  if (opt_process != NULL)
    {
      gint pid;
      guint64 pid_start_time;

      if (sscanf (opt_process, "%i,%" G_GUINT64_FORMAT, &pid, &pid_start_time)
	  == 2)
151 152 153 154 155
	{
	  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
          subject = polkit_unix_process_new_full (pid, pid_start_time);
	  G_GNUC_END_IGNORE_DEPRECATIONS
	}
Miloslav Trmač's avatar
Miloslav Trmač committed
156
      else if (sscanf (opt_process, "%i", &pid) == 1)
157 158 159 160 161
	{
	  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
	  subject = polkit_unix_process_new (pid);
	  G_GNUC_END_IGNORE_DEPRECATIONS
	}
Miloslav Trmač's avatar
Miloslav Trmač committed
162 163 164 165 166 167 168 169 170
      else
	{
	  g_printerr (_("%s: Invalid process specifier `%s'\n"),
		      g_get_prgname (), opt_process);
	  goto out;
	}
    }
  if (opt_system_bus_name != NULL)
    subject = polkit_system_bus_name_new (opt_system_bus_name);
David Zeuthen's avatar
David Zeuthen committed
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 196 197
  /* Use parent process, if no subject has been specified */
  if (subject == NULL)
    {
      pid_t pid_of_caller;
      pid_of_caller = getppid ();
      if (pid_of_caller == 1)
        {
          /* getppid() can return 1 if the parent died (meaning that we are reaped
           * by /sbin/init); In that case we simpy bail.
           */
          g_printerr ("Refusing to render service to dead parents.\n");
          goto out;
        }

      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);
    }

  authority = polkit_authority_get_sync (NULL /* GCancellable* */, &error);
  if (authority == NULL)
    {
198 199
      g_printerr ("Authorization not available. Check if polkit service is running or see debug message for more information.\n");
      g_debug ("Error getting authority: %s (%s, %d)\n",
David Zeuthen's avatar
David Zeuthen committed
200 201 202 203 204 205
                  error->message, g_quark_to_string (error->domain), error->code);
      g_error_free (error);
      ret = 127;
      goto out;
    }

206 207 208 209
  if (opt_fallback)
    {
      g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
      g_variant_builder_add (&builder, "{sv}", "fallback", g_variant_new_boolean (TRUE));
Miloslav Trmač's avatar
Miloslav Trmač committed
210
      listener_options = g_variant_builder_end (&builder);
211 212
    }

David Zeuthen's avatar
David Zeuthen committed
213 214 215 216 217 218 219 220 221 222 223
  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 (%s, %d)\n",
                  error->message, g_quark_to_string (error->domain), error->code);
      g_error_free (error);
      ret = 127;
      goto out;
    }
224 225 226 227
  local_agent_handle = polkit_agent_listener_register_with_options (listener,
                                                                    POLKIT_AGENT_REGISTER_FLAGS_RUN_IN_THREAD,
                                                                    subject,
                                                                    NULL, /* object_path */
Miloslav Trmač's avatar
Miloslav Trmač committed
228
                                                                    listener_options,
229 230
                                                                    NULL, /* GCancellable */
                                                                    &error);
Miloslav Trmač's avatar
Miloslav Trmač committed
231
  listener_options = NULL; /* consumed */
David Zeuthen's avatar
David Zeuthen committed
232 233 234 235 236 237 238 239 240
  g_object_unref (listener);
  if (local_agent_handle == NULL)
    {
      g_printerr ("Error registering authentication agent: %s (%s, %d)\n",
                  error->message, g_quark_to_string (error->domain), error->code);
      g_error_free (error);
      goto out;
    }

Miloslav Trmač's avatar
Miloslav Trmač committed
241
  if (opt_notify_fd != -1)
David Zeuthen's avatar
David Zeuthen committed
242
    {
Miloslav Trmač's avatar
Miloslav Trmač committed
243
      if (close (opt_notify_fd) != 0)
David Zeuthen's avatar
David Zeuthen committed
244
        {
Miloslav Trmač's avatar
Miloslav Trmač committed
245
          g_printerr ("Error closing notify-fd %d: %m\n", opt_notify_fd);
David Zeuthen's avatar
David Zeuthen committed
246 247 248 249
          goto out;
        }
    }

250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
/* Bash leaves tty echo disabled if SIGINT/SIGTERM comes to polkitagenttextlistener.c::on_request(),
   but due to threading the handlers cannot take care of the signal there.
   Though if controlling terminal cannot be found, the world won't stop spinning.
*/
  tty_name = ctermid(NULL);
  if (tty_name != NULL)
  {
    tty = fopen(tty_name, "r+");
  }

  if (tty != NULL && !tcgetattr (fileno (tty), &ts))
  {
	  tty_flags_saved = TRUE;
  }

  memset (&sa, 0, sizeof (sa));
  sa.sa_handler = &tty_handler;
267 268 269 270 271 272
/* If tty_handler() resets terminal while pkttyagent is run in background job,
   the process gets stopped by SIGTTOU. This impacts systemctl, hence it must
   be blocked for a while and then the process gets killed anyway.
 */
  sigemptyset(&sa.sa_mask);
  sigaddset(&sa.sa_mask, SIGTTOU);
273 274 275 276
  sigaction (SIGTERM, &sa, &savesigterm);
  sigaction (SIGINT, &sa, &savesigint);
  sigaction (SIGTSTP, &sa, &savesigtstp);

David Zeuthen's avatar
David Zeuthen committed
277 278 279 280 281 282 283 284 285 286
  loop = g_main_loop_new (NULL, FALSE);
  g_main_loop_run (loop);

 out:
  if (loop != NULL)
    g_main_loop_unref (loop);

  if (local_agent_handle != NULL)
    polkit_agent_listener_unregister (local_agent_handle);

Miloslav Trmač's avatar
Miloslav Trmač committed
287 288
  if (listener_options != NULL)
    g_variant_unref (listener_options);
289

David Zeuthen's avatar
David Zeuthen committed
290 291 292 293 294 295
  if (subject != NULL)
    g_object_unref (subject);

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

Miloslav Trmač's avatar
Miloslav Trmač committed
296 297 298 299
  g_free (opt_process);
  g_free (opt_system_bus_name);
  g_option_context_free (context);

David Zeuthen's avatar
David Zeuthen committed
300 301
  return ret;
}