/* * 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 */ /** * SECTION:polkitagentsession * @title: PolkitAgentSession * @short_description: Authentication Session * @stability: Unstable * * The #PolkitAgentSession class is an abstraction used for interacting with the * native authentication system (for example PAM) for obtaining authorizations. * This class is typically used together with instances that are derived from * the #PolkitAgentListener abstract base class. * * To perform the actual authentication, #PolkitAgentSession uses a trusted suid helper. * The authentication conversation is done through a pipe. This is transparent; the user * only need to handle the * #PolkitAgentSession::request, * #PolkitAgentSession::show-info, * #PolkitAgentSession::show-error and * #PolkitAgentSession::completed * signals and invoke polkit_agent_session_response() in response to requests. * * If the user successfully authenticates, the authentication helper will invoke * a method on the PolicyKit daemon (see polkit_authority_authentication_agent_response_sync()) * with the given @cookie. Upon receiving a positive response from the PolicyKit daemon (via * the authentication helper), the #PolkitAgentSession::completed signal will be emitted * with the @gained_authorization paramter set to %TRUE. * * If the user is unable to authenticate, the #PolkitAgentSession::completed signal will * be emitted with the @gained_authorization paramter set to %FALSE. */ #include "config.h" #include #include #include #include #include #include #include #include "polkitagentmarshal.h" #include "polkitagentsession.h" static gboolean _show_debug (void) { static volatile gsize has_show_debug = 0; static gboolean show_debug_value = FALSE; if (g_once_init_enter (&has_show_debug)) { show_debug_value = (g_getenv ("POLKIT_DEBUG") != NULL); g_once_init_leave (&has_show_debug, 1); } return show_debug_value; } /** * PolkitAgentSession: * * The #PolkitAgentSession struct should not be accessed directly. */ struct _PolkitAgentSession { /*< private >*/ GObject parent_instance; gchar *cookie; PolkitIdentity *identity; GOutputStream *child_stdin; int child_stdout; GPid child_pid; GSource *child_stdout_watch_source; GIOChannel *child_stdout_channel; gboolean success; gboolean helper_is_running; gboolean have_emitted_completed; }; struct _PolkitAgentSessionClass { GObjectClass parent_class; }; enum { PROP_0, PROP_IDENTITY, PROP_COOKIE }; enum { REQUEST_SIGNAL, SHOW_INFO_SIGNAL, SHOW_ERROR_SIGNAL, COMPLETED_SIGNAL, LAST_SIGNAL, }; static guint signals[LAST_SIGNAL] = {0}; G_DEFINE_TYPE (PolkitAgentSession, polkit_agent_session, G_TYPE_OBJECT); static void polkit_agent_session_init (PolkitAgentSession *session) { session->child_stdout = -1; } static void kill_helper (PolkitAgentSession *session); static void polkit_agent_session_finalize (GObject *object) { PolkitAgentSession *session; session = POLKIT_AGENT_SESSION (object); /* this releases resources related to the helper */ kill_helper (session); g_free (session->cookie); if (session->identity != NULL) g_object_unref (session->identity); if (G_OBJECT_CLASS (polkit_agent_session_parent_class)->finalize != NULL) G_OBJECT_CLASS (polkit_agent_session_parent_class)->finalize (object); } static void polkit_agent_session_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { PolkitAgentSession *session = POLKIT_AGENT_SESSION (object); switch (prop_id) { case PROP_IDENTITY: g_value_set_object (value, session->identity); break; case PROP_COOKIE: g_value_set_string (value, session->cookie); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void polkit_agent_session_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { PolkitAgentSession *session = POLKIT_AGENT_SESSION (object); switch (prop_id) { case PROP_IDENTITY: session->identity = g_value_dup_object (value); break; case PROP_COOKIE: session->cookie = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void polkit_agent_session_class_init (PolkitAgentSessionClass *klass) { GObjectClass *gobject_class; gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = polkit_agent_session_finalize; gobject_class->get_property = polkit_agent_session_get_property; gobject_class->set_property = polkit_agent_session_set_property; /** * PolkitAgentSession:identity: * * The identity to authenticate. */ g_object_class_install_property (gobject_class, PROP_IDENTITY, g_param_spec_object ("identity", "Identity", "The identity to authenticate", POLKIT_TYPE_IDENTITY, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK)); /** * PolkitAgentSession:cookie: * * The cookie obtained from the PolicyKit daemon */ g_object_class_install_property (gobject_class, PROP_COOKIE, g_param_spec_string ("cookie", "Cookie", "The cookie obtained from the PolicyKit daemon", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK)); /** * PolkitAgentSession::request: * @session: A #PolkitAgentSession. * @request: The request to show the user, e.g. "name: " or "password: ". * @echo_on: %TRUE if the response to the request SHOULD be echoed on the * screen, %FALSE if the response MUST NOT be echoed to the screen. * * Emitted when the user is requested to answer a question. * * When the response has been collected from the user, call polkit_agent_session_response(). */ signals[REQUEST_SIGNAL] = g_signal_new ("request", POLKIT_AGENT_TYPE_SESSION, G_SIGNAL_RUN_LAST, 0, /* class offset */ NULL, /* accumulator */ NULL, /* accumulator data */ _polkit_agent_marshal_VOID__STRING_BOOLEAN, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_BOOLEAN); /** * PolkitAgentSession::show-info: * @session: A #PolkitAgentSession. * @text: A string to display to the user. * * Emitted when there is information to be displayed to the user. */ signals[SHOW_INFO_SIGNAL] = g_signal_new ("show-info", POLKIT_AGENT_TYPE_SESSION, G_SIGNAL_RUN_LAST, 0, /* class offset */ NULL, /* accumulator */ NULL, /* accumulator data */ g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); /** * PolkitAgentSession::show-error: * @session: A #PolkitAgentSession. * @text: An error string to display to the user. * * Emitted when there is information related to an error condition to be displayed to the user. */ signals[SHOW_ERROR_SIGNAL] = g_signal_new ("show-error", POLKIT_AGENT_TYPE_SESSION, G_SIGNAL_RUN_LAST, 0, /* class offset */ NULL, /* accumulator */ NULL, /* accumulator data */ g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); /** * PolkitAgentSession::completed: * @session: A #PolkitAgentSession. * @gained_authorization: %TRUE only if the authorization was successfully obtained. * * Emitted when the authentication session has been completed or * cancelled. The @gained_authorization parameter is %TRUE only if * the user successfully authenticated. * * Upon receiving this signal, the user should free @session using g_object_unref(). */ signals[COMPLETED_SIGNAL] = g_signal_new ("completed", POLKIT_AGENT_TYPE_SESSION, G_SIGNAL_RUN_LAST, 0, /* class offset */ NULL, /* accumulator */ NULL, /* accumulator data */ g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); } /** * polkit_agent_session_new: * @identity: The identity to authenticate. * @cookie: The cookie obtained from the PolicyKit daemon * * Creates a new authentication session. * * The caller should connect to the * #PolkitAgentSession::request, * #PolkitAgentSession::show-info, * #PolkitAgentSession::show-error and * #PolkitAgentSession::completed * signals and then call polkit_agent_session_initiate() to initiate the authentication session. * * Returns: A #PolkitAgentSession. Free with g_object_unref(). **/ PolkitAgentSession * polkit_agent_session_new (PolkitIdentity *identity, const gchar *cookie) { PolkitAgentSession *session; g_return_val_if_fail (POLKIT_IS_IDENTITY (identity), NULL); g_return_val_if_fail (cookie != NULL, NULL); session = POLKIT_AGENT_SESSION (g_object_new (POLKIT_AGENT_TYPE_SESSION, "identity", identity, "cookie", cookie, NULL)); return session; } static void kill_helper (PolkitAgentSession *session) { if (!session->helper_is_running) goto out; if (session->child_pid > 0) { gint status; //g_debug ("Sending SIGTERM to helper"); kill (session->child_pid, SIGTERM); waitpid (session->child_pid, &status, 0); session->child_pid = 0; } if (session->child_stdout_watch_source != NULL) { g_source_destroy (session->child_stdout_watch_source); g_source_unref (session->child_stdout_watch_source); session->child_stdout_watch_source = NULL; } if (session->child_stdout_channel != NULL) { g_io_channel_unref (session->child_stdout_channel); session->child_stdout_channel = NULL; } if (session->child_stdout != -1) { g_warn_if_fail (close (session->child_stdout) == 0); session->child_stdout = -1; } g_clear_object (&session->child_stdin); session->helper_is_running = FALSE; out: ; } static void complete_session (PolkitAgentSession *session, gboolean result) { kill_helper (session); if (!session->have_emitted_completed) { if (G_UNLIKELY (_show_debug ())) g_print ("PolkitAgentSession: emitting ::completed(%s)\n", result ? "TRUE" : "FALSE"); session->have_emitted_completed = TRUE; /* Note that the signal handler may drop the last reference to session. */ g_signal_emit_by_name (session, "completed", result); } } static gboolean io_watch_have_data (GIOChannel *channel, GIOCondition condition, gpointer user_data) { PolkitAgentSession *session = POLKIT_AGENT_SESSION (user_data); gchar *line, *unescaped; GError *error; error = NULL; line = NULL; unescaped = NULL; if (!session->helper_is_running) { g_warning ("in io_watch_have_data() but helper is not supposed to be running"); complete_session (session, FALSE); goto out; } g_io_channel_read_line (channel, &line, NULL, NULL, &error); if (error != NULL || line == NULL) { /* In case we get just G_IO_HUP, line is NULL but error is unset.*/ g_warning ("Error reading line from helper: %s", error ? error->message : "nothing to read"); g_clear_error (&error); complete_session (session, FALSE); goto out; } /* remove terminator */ if (strlen (line) > 0 && line[strlen (line) - 1] == '\n') line[strlen (line) - 1] = '\0'; unescaped = g_strcompress (line); if (G_UNLIKELY (_show_debug ())) g_print ("PolkitAgentSession: read `%s' from helper\n", unescaped); if (g_str_has_prefix (unescaped, "PAM_PROMPT_ECHO_OFF ")) { const gchar *s = unescaped + sizeof "PAM_PROMPT_ECHO_OFF " - 1; if (G_UNLIKELY (_show_debug ())) g_print ("PolkitAgentSession: emitting ::request('%s', FALSE)\n", s); g_signal_emit_by_name (session, "request", s, FALSE); } else if (g_str_has_prefix (unescaped, "PAM_PROMPT_ECHO_ON ")) { const gchar *s = unescaped + sizeof "PAM_PROMPT_ECHO_ON " - 1; if (G_UNLIKELY (_show_debug ())) g_print ("PolkitAgentSession: emitting ::request('%s', TRUE)\n", s); g_signal_emit_by_name (session, "request", s, TRUE); } else if (g_str_has_prefix (unescaped, "PAM_ERROR_MSG ")) { const gchar *s = unescaped + sizeof "PAM_ERROR_MSG " - 1; if (G_UNLIKELY (_show_debug ())) g_print ("PolkitAgentSession: emitting ::show-error('%s')\n", s); g_signal_emit_by_name (session, "show-error", s); } else if (g_str_has_prefix (unescaped, "PAM_TEXT_INFO ")) { const gchar *s = unescaped + sizeof "PAM_TEXT_INFO " - 1; if (G_UNLIKELY (_show_debug ())) g_print ("PolkitAgentSession: emitting ::show-info('%s')\n", s); g_signal_emit_by_name (session, "show-info", s); } else if (g_str_has_prefix (unescaped, "SUCCESS")) { complete_session (session, TRUE); } else if (g_str_has_prefix (unescaped, "FAILURE")) { complete_session (session, FALSE); } else { g_warning ("Unknown line '%s' from helper", line); complete_session (session, FALSE); goto out; } out: g_free (line); g_free (unescaped); if (condition & (G_IO_ERR | G_IO_HUP)) complete_session (session, FALSE); /* keep the IOChannel around */ return TRUE; } /** * polkit_agent_session_response: * @session: A #PolkitAgentSession. * @response: Response from the user, typically a password. * * Function for providing response to requests received * via the #PolkitAgentSession::request signal. **/ void polkit_agent_session_response (PolkitAgentSession *session, const gchar *response) { gboolean add_newline; size_t response_len; const char newline[] = "\n"; g_return_if_fail (POLKIT_AGENT_IS_SESSION (session)); g_return_if_fail (response != NULL); response_len = strlen (response); add_newline = (response_len == 0 || response[response_len - 1] != '\n'); (void) g_output_stream_write_all (session->child_stdin, response, response_len, NULL, NULL, NULL); if (add_newline) (void) g_output_stream_write_all (session->child_stdin, newline, 1, NULL, NULL, NULL); } /** * polkit_agent_session_initiate: * @session: A #PolkitAgentSession. * * Initiates the authentication session. Before calling this method, * make sure to connect to the various signals. The signals will be * emitted in the thread-default main * loop that this method is invoked from. * * Use polkit_agent_session_cancel() to cancel the session. **/ void polkit_agent_session_initiate (PolkitAgentSession *session) { uid_t uid; GError *error; gchar *helper_argv[3]; struct passwd *passwd; int stdin_fd = -1; g_return_if_fail (POLKIT_AGENT_IS_SESSION (session)); if (G_UNLIKELY (_show_debug ())) { gchar *s; s = polkit_identity_to_string (session->identity); g_print ("PolkitAgentSession: initiating authentication for identity `%s', cookie %s\n", s, session->cookie); g_free (s); } /* TODO: also support authorization for other kinds of identities */ if (!POLKIT_IS_UNIX_USER (session->identity)) { g_warning ("Unsupported identity type"); goto error; } uid = polkit_unix_user_get_uid (POLKIT_UNIX_USER (session->identity)); passwd = getpwuid (uid); if (passwd == NULL) { g_warning ("No user with uid %d", uid); goto error; } helper_argv[0] = PACKAGE_PREFIX "/lib/polkit-1/polkit-agent-helper-1"; helper_argv[1] = passwd->pw_name; helper_argv[2] = NULL; session->child_stdout = -1; error = NULL; if (!g_spawn_async_with_pipes (NULL, (char **) helper_argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD | 0,//G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, &session->child_pid, &stdin_fd, &session->child_stdout, NULL, &error)) { g_warning ("Cannot spawn helper: %s\n", error->message); g_error_free (error); goto error; } if (G_UNLIKELY (_show_debug ())) g_print ("PolkitAgentSession: spawned helper with pid %d\n", (gint) session->child_pid); session->child_stdin = (GOutputStream*)g_unix_output_stream_new (stdin_fd, TRUE); /* Write the cookie on stdin so it can't be seen by other processes */ (void) g_output_stream_write_all (session->child_stdin, session->cookie, strlen (session->cookie), NULL, NULL, NULL); (void) g_output_stream_write_all (session->child_stdin, "\n", 1, NULL, NULL, NULL); session->child_stdout_channel = g_io_channel_unix_new (session->child_stdout); session->child_stdout_watch_source = g_io_create_watch (session->child_stdout_channel, G_IO_IN | G_IO_ERR | G_IO_HUP); g_source_set_callback (session->child_stdout_watch_source, (GSourceFunc) io_watch_have_data, session, NULL); g_source_attach (session->child_stdout_watch_source, g_main_context_get_thread_default ()); session->success = FALSE; session->helper_is_running = TRUE; return; error: complete_session (session, FALSE); } /** * polkit_agent_session_cancel: * @session: A #PolkitAgentSession. * * Cancels an authentication session. This will make @session emit the #PolkitAgentSession::completed * signal. **/ void polkit_agent_session_cancel (PolkitAgentSession *session) { g_return_if_fail (POLKIT_AGENT_IS_SESSION (session)); if (G_UNLIKELY (_show_debug ())) g_print ("PolkitAgentSession: canceling authentication\n"); complete_session (session, FALSE); }