From b8cad064ebe02ae1d2fc4da85e16686d51b08e07 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 2 Dec 2018 17:31:37 -0500 Subject: [PATCH] Add our own polkit listener implementation This lets us respect the fancy output setting, and it lets us do some other things that make it better integrated. Closes: #2379 Approved by: alexlarsson --- app/Makefile.am.inc | 2 + app/flatpak-main.c | 8 +- app/flatpak-polkit-agent-text-listener.c | 495 +++++++++++++++++++++++ app/flatpak-polkit-agent-text-listener.h | 24 ++ common/flatpak-utils-private.h | 3 +- common/flatpak-utils.c | 3 + 6 files changed, 530 insertions(+), 5 deletions(-) create mode 100644 app/flatpak-polkit-agent-text-listener.c create mode 100644 app/flatpak-polkit-agent-text-listener.h diff --git a/app/Makefile.am.inc b/app/Makefile.am.inc index 22d8c05a..637e647e 100644 --- a/app/Makefile.am.inc +++ b/app/Makefile.am.inc @@ -20,6 +20,8 @@ app/%-dbus-generated.h: app/%-dbus-generated.c flatpak_SOURCES = \ app/flatpak-main.c \ + app/flatpak-polkit-agent-text-listener.h \ + app/flatpak-polkit-agent-text-listener.c \ app/flatpak-builtins.h \ app/flatpak-builtins-utils.h \ app/flatpak-builtins-utils.c \ diff --git a/app/flatpak-main.c b/app/flatpak-main.c index 9ac98282..07484caa 100644 --- a/app/flatpak-main.c +++ b/app/flatpak-main.c @@ -32,8 +32,7 @@ #ifdef USE_SYSTEM_HELPER #include -#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE -#include +#include "flatpak-polkit-agent-text-listener.h" #endif #include "flatpak-builtins.h" @@ -638,7 +637,7 @@ main (int argc, #ifdef USE_SYSTEM_HELPER /* Install a polkit agent as fallback, in case we're running on a console */ - listener = polkit_agent_text_listener_new (NULL, &error); + listener = flatpak_polkit_agent_text_listener_new (NULL, &error); if (listener == NULL) { g_debug ("Failed to create polkit agent listener: %s", error->message); @@ -653,7 +652,8 @@ main (int argc, subject = polkit_unix_process_new_for_owner (getpid (), 0, -1); g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); - g_variant_builder_add (&opt_builder, "{sv}", "fallback", g_variant_new_boolean (TRUE)); + if (g_strcmp0 (g_getenv ("FLATPAK_FORCE_TEXT_AUTH"), "1") != 0) + g_variant_builder_add (&opt_builder, "{sv}", "fallback", g_variant_new_boolean (TRUE)); options = g_variant_ref_sink (g_variant_builder_end (&opt_builder)); agent = polkit_agent_listener_register_with_options (listener, diff --git a/app/flatpak-polkit-agent-text-listener.c b/app/flatpak-polkit-agent-text-listener.c new file mode 100644 index 00000000..237efcbe --- /dev/null +++ b/app/flatpak-polkit-agent-text-listener.c @@ -0,0 +1,495 @@ +/* + * 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 + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "flatpak-polkit-agent-text-listener.h" + +#include "flatpak-utils-private.h" + +struct _FlatpakPolkitAgentTextListener +{ + PolkitAgentListener parent_instance; + + GSimpleAsyncResult *simple; + PolkitAgentSession *active_session; + gulong cancel_id; + GCancellable *cancellable; + + FILE *tty; +}; + +typedef struct +{ + PolkitAgentListenerClass parent_class; +} FlatpakPolkitAgentTextListenerClass; + +static void flatpak_polkit_agent_text_listener_initiate_authentication (PolkitAgentListener *_listener, + const gchar *action_id, + const gchar *message, + const gchar *icon_name, + PolkitDetails *details, + const gchar *cookie, + GList *identities, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +static gboolean flatpak_polkit_agent_text_listener_initiate_authentication_finish (PolkitAgentListener *_listener, + GAsyncResult *res, + GError **error); + +static void initable_iface_init (GInitableIface *initable_iface); + +G_DEFINE_TYPE_WITH_CODE (FlatpakPolkitAgentTextListener, flatpak_polkit_agent_text_listener, POLKIT_AGENT_TYPE_LISTENER, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)); + +static void +flatpak_polkit_agent_text_listener_init (FlatpakPolkitAgentTextListener *listener) +{ +} + +static void +flatpak_polkit_agent_text_listener_finalize (GObject *object) +{ + FlatpakPolkitAgentTextListener *listener = FLATPAK_POLKIT_AGENT_TEXT_LISTENER (object); + + if (listener->tty != NULL) + fclose (listener->tty); + + if (listener->active_session != NULL) + g_object_unref (listener->active_session); + + if (G_OBJECT_CLASS (flatpak_polkit_agent_text_listener_parent_class)->finalize != NULL) + G_OBJECT_CLASS (flatpak_polkit_agent_text_listener_parent_class)->finalize (object); +} + +static void +flatpak_polkit_agent_text_listener_class_init (FlatpakPolkitAgentTextListenerClass *klass) +{ + GObjectClass *gobject_class; + PolkitAgentListenerClass *listener_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = flatpak_polkit_agent_text_listener_finalize; + + listener_class = POLKIT_AGENT_LISTENER_CLASS (klass); + listener_class->initiate_authentication = flatpak_polkit_agent_text_listener_initiate_authentication; + listener_class->initiate_authentication_finish = flatpak_polkit_agent_text_listener_initiate_authentication_finish; +} + +PolkitAgentListener * +flatpak_polkit_agent_text_listener_new (GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + return POLKIT_AGENT_LISTENER (g_initable_new (FLATPAK_POLKIT_AGENT_TYPE_TEXT_LISTENER, + cancellable, + error, + NULL)); +} + +static gboolean +initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + FlatpakPolkitAgentTextListener *listener = FLATPAK_POLKIT_AGENT_TEXT_LISTENER (initable); + gboolean ret; + const gchar *tty_name; + + ret = FALSE; + + tty_name = ctermid (NULL); + if (tty_name == NULL) + { + g_set_error (error, POLKIT_ERROR, POLKIT_ERROR_FAILED, + "Cannot determine pathname for current controlling terminal for the process: %s", + strerror (errno)); + goto out; + } + + listener->tty = fopen (tty_name, "r+"); + if (listener->tty == NULL) + { + g_set_error (error, POLKIT_ERROR, POLKIT_ERROR_FAILED, + "Error opening current controlling terminal for the process (`%s'): %s", + tty_name, strerror (errno)); + goto out; + } + + ret = TRUE; + + out: + return ret; +} + +static void +initable_iface_init (GInitableIface *initable_iface) +{ + initable_iface->init = initable_init; +} + +static void +on_completed (PolkitAgentSession *session, + gboolean gained_authorization, + gpointer user_data) +{ + FlatpakPolkitAgentTextListener *listener = FLATPAK_POLKIT_AGENT_TEXT_LISTENER (user_data); + + if (flatpak_fancy_output ()) + fprintf (listener->tty, FLATPAK_ANSI_RED); + if (gained_authorization) + fprintf (listener->tty, "==== AUTHENTICATION COMPLETE ====\n"); + else + fprintf (listener->tty, "==== AUTHENTICATION FAILED ====\n"); + if (flatpak_fancy_output ()) + { + sleep (1); /* show the message for a bit */ + fprintf (listener->tty, FLATPAK_ANSI_COLOR_RESET FLATPAK_ANSI_ALT_SCREEN_OFF); + } + fflush (listener->tty); + + g_simple_async_result_complete_in_idle (listener->simple); + + g_object_unref (listener->simple); + g_object_unref (listener->active_session); + g_cancellable_disconnect (listener->cancellable, listener->cancel_id); + g_object_unref (listener->cancellable); + + listener->simple = NULL; + listener->active_session = NULL; + listener->cancel_id = 0; +} + +static void +on_request (PolkitAgentSession *session, + const gchar *request, + gboolean echo_on, + gpointer user_data) +{ + FlatpakPolkitAgentTextListener *listener = FLATPAK_POLKIT_AGENT_TEXT_LISTENER (user_data); + struct termios ts, ots; + GString *str; + + fprintf (listener->tty, "%s", request); + fflush (listener->tty); + + setbuf (listener->tty, NULL); + + tcgetattr (fileno (listener->tty), &ts); + ots = ts; + ts.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); + tcsetattr (fileno (listener->tty), TCSAFLUSH, &ts); + + str = g_string_new (NULL); + while (TRUE) + { + gint c; + c = getc (listener->tty); + if (c == '\n') + { + /* ok, done */ + break; + } + else if (c == EOF) + { + tcsetattr (fileno (listener->tty), TCSAFLUSH, &ots); + g_error ("Got unexpected EOF while reading from controlling terminal."); + abort (); + break; + } + else + { + g_string_append_c (str, c); + } + } + tcsetattr (fileno (listener->tty), TCSAFLUSH, &ots); + putc ('\n', listener->tty); + + polkit_agent_session_response (session, str->str); + memset (str->str, '\0', str->len); + g_string_free (str, TRUE); +} + +static void +on_show_error (PolkitAgentSession *session, + const gchar *text, + gpointer user_data) +{ + FlatpakPolkitAgentTextListener *listener = FLATPAK_POLKIT_AGENT_TEXT_LISTENER (user_data); + fprintf (listener->tty, "Error: %s\n", text); + fflush (listener->tty); +} + +static void +on_show_info (PolkitAgentSession *session, + const gchar *text, + gpointer user_data) +{ + FlatpakPolkitAgentTextListener *listener = FLATPAK_POLKIT_AGENT_TEXT_LISTENER (user_data); + fprintf (listener->tty, "Info: %s\n", text); + fflush (listener->tty); +} + +static void +on_cancelled (GCancellable *cancellable, + gpointer user_data) +{ + FlatpakPolkitAgentTextListener *listener = FLATPAK_POLKIT_AGENT_TEXT_LISTENER (user_data); + fprintf (listener->tty, "Cancelled\n"); + fflush (listener->tty); + polkit_agent_session_cancel (listener->active_session); +} + +static gchar * +identity_to_human_readable_string (PolkitIdentity *identity) +{ + gchar *ret; + + g_return_val_if_fail (POLKIT_IS_IDENTITY (identity), NULL); + + ret = NULL; + if (POLKIT_IS_UNIX_USER (identity)) + { + struct passwd pw; + struct passwd *ppw; + char buf[2048]; + int res; + + res = getpwuid_r (polkit_unix_user_get_uid (POLKIT_UNIX_USER (identity)), + &pw, + buf, + sizeof buf, + &ppw); + if (res != 0) + { + g_warning ("Error calling getpwuid_r: %s", strerror (res)); + } + else + { + if (ppw->pw_gecos == NULL || strlen (ppw->pw_gecos) == 0 || strcmp (ppw->pw_gecos, ppw->pw_name) == 0) + { + ret = g_strdup_printf ("%s", ppw->pw_name); + } + else + { + ret = g_strdup_printf ("%s (%s)", ppw->pw_gecos, ppw->pw_name); + } + } + } + if (ret == NULL) + ret = polkit_identity_to_string (identity); + return ret; +} + +static PolkitIdentity * +choose_identity (FlatpakPolkitAgentTextListener *listener, + GList *identities) +{ + GList *l; + guint n; + guint num_identities; + GString *str; + PolkitIdentity *ret; + guint num; + gchar *endp; + + ret = NULL; + + fprintf (listener->tty, "Multiple identities can be used for authentication:\n"); + for (l = identities, n = 0; l != NULL; l = l->next, n++) + { + PolkitIdentity *identity = POLKIT_IDENTITY (l->data); + gchar *s; + s = identity_to_human_readable_string (identity); + fprintf (listener->tty, " %d. %s\n", n + 1, s); + g_free (s); + } + num_identities = n; + fprintf (listener->tty, "Choose identity to authenticate as (1-%d): ", num_identities); + fflush (listener->tty); + + str = g_string_new (NULL); + while (TRUE) + { + gint c; + c = getc (listener->tty); + if (c == '\n') + { + /* ok, done */ + break; + } + else if (c == EOF) + { + g_error ("Got unexpected EOF while reading from controlling terminal."); + abort (); + break; + } + else + { + g_string_append_c (str, c); + } + } + + num = strtol (str->str, &endp, 10); + if (str->len == 0 || *endp != '\0' || (num < 1 || num > num_identities)) + { + fprintf (listener->tty, "Invalid response `%s'.\n", str->str); + goto out; + } + + ret = g_list_nth_data (identities, num-1); + + out: + g_string_free (str, TRUE); + return ret; +} + + +static void +flatpak_polkit_agent_text_listener_initiate_authentication (PolkitAgentListener *_listener, + const gchar *action_id, + const gchar *message, + const gchar *icon_name, + PolkitDetails *details, + const gchar *cookie, + GList *identities, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + FlatpakPolkitAgentTextListener *listener = FLATPAK_POLKIT_AGENT_TEXT_LISTENER (_listener); + GSimpleAsyncResult *simple; + PolkitIdentity *identity; + + simple = g_simple_async_result_new (G_OBJECT (listener), + callback, + user_data, + flatpak_polkit_agent_text_listener_initiate_authentication); + if (listener->active_session != NULL) + { + g_simple_async_result_set_error (simple, POLKIT_ERROR, POLKIT_ERROR_FAILED, + "An authentication session is already underway."); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + goto out; + } + + g_assert (g_list_length (identities) >= 1); + + if (flatpak_fancy_output ()) + { + fprintf (listener->tty, FLATPAK_ANSI_ALT_SCREEN_ON FLATPAK_ANSI_RED); + } + + fprintf (listener->tty, "==== AUTHENTICATING FOR %s ====\n", action_id); + + if (flatpak_fancy_output ()) + fprintf (listener->tty, FLATPAK_ANSI_COLOR_RESET); + + fprintf (listener->tty, "%s\n", message); + + /* handle multiple identies by asking which one to use */ + if (g_list_length (identities) > 1) + { + identity = choose_identity (listener, identities); + if (identity == NULL) + { + fprintf (listener->tty, "\x1B[1;31m"); + fprintf (listener->tty, "==== AUTHENTICATION CANCELED ====\n"); + fprintf (listener->tty, "\x1B[0m"); + fflush (listener->tty); + g_simple_async_result_set_error (simple, + POLKIT_ERROR, + POLKIT_ERROR_FAILED, + "Authentication was canceled."); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + goto out; + } + } + else + { + gchar *s; + identity = identities->data; + s = identity_to_human_readable_string (identity); + fprintf (listener->tty, + "Authenticating as: %s\n", + s); + g_free (s); + } + + listener->active_session = polkit_agent_session_new (identity, cookie); + g_signal_connect (listener->active_session, + "completed", + G_CALLBACK (on_completed), + listener); + g_signal_connect (listener->active_session, + "request", + G_CALLBACK (on_request), + listener); + g_signal_connect (listener->active_session, + "show-info", + G_CALLBACK (on_show_info), + listener); + g_signal_connect (listener->active_session, + "show-error", + G_CALLBACK (on_show_error), + listener); + + listener->simple = simple; + listener->cancellable = g_object_ref (cancellable); + listener->cancel_id = g_cancellable_connect (cancellable, + G_CALLBACK (on_cancelled), + listener, + NULL); + + polkit_agent_session_initiate (listener->active_session); + + out: + ; +} + +static gboolean +flatpak_polkit_agent_text_listener_initiate_authentication_finish (PolkitAgentListener *_listener, + GAsyncResult *res, + GError **error) +{ + g_warn_if_fail (g_simple_async_result_get_source_tag (G_SIMPLE_ASYNC_RESULT (res)) == + flatpak_polkit_agent_text_listener_initiate_authentication); + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return FALSE; + + return TRUE; +} diff --git a/app/flatpak-polkit-agent-text-listener.h b/app/flatpak-polkit-agent-text-listener.h new file mode 100644 index 00000000..77594266 --- /dev/null +++ b/app/flatpak-polkit-agent-text-listener.h @@ -0,0 +1,24 @@ +#ifndef __FLATPAK_POLKIT_AGENT_TEXT_LISTENER_H +#define __FLATPAK_POLKIT_AGENT_TEXT_LISTENER_H + +#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE +#include +#include + +G_BEGIN_DECLS + +typedef struct _FlatpakPolkitAgentTextListener FlatpakPolkitAgentTextListener; + +#define FLATPAK_POLKIT_AGENT_TYPE_TEXT_LISTENER (flatpak_polkit_agent_text_listener_get_type()) +#define FLATPAK_POLKIT_AGENT_TEXT_LISTENER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), FLATPAK_POLKIT_AGENT_TYPE_TEXT_LISTENER, FlatpakPolkitAgentTextListener)) +#define FLATPAK_POLKIT_AGENT_IS_TEXT_LISTENER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), FLATPAK_POLKIT_AGENT_TYPE_TEXT_LISTENER)) + +GType flatpak_polkit_agent_text_listener_get_type (void) G_GNUC_CONST; +PolkitAgentListener *flatpak_polkit_agent_text_listener_new (GCancellable *cancellable, + GError **error); + + +G_END_DECLS + +#endif /* __FLATPAK_POLKIT_AGENT_TEXT_LISTENER_H */ + diff --git a/common/flatpak-utils-private.h b/common/flatpak-utils-private.h index 156462e2..ada6a946 100644 --- a/common/flatpak-utils-private.h +++ b/common/flatpak-utils-private.h @@ -42,7 +42,8 @@ typedef enum { FLATPAK_HOST_COMMAND_FLAGS_WATCH_BUS = 1 << 1, } FlatpakHostCommandFlags; - +#define FLATPAK_ANSI_ALT_SCREEN_ON "\x1B[?1049h" +#define FLATPAK_ANSI_ALT_SCREEN_OFF "\x1B[?1049l" #define FLATPAK_ANSI_BOLD_ON "\x1b[1m" #define FLATPAK_ANSI_BOLD_OFF "\x1b[22m" #define FLATPAK_ANSI_RED "\x1b[31m" diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c index dde9539d..736919bc 100644 --- a/common/flatpak-utils.c +++ b/common/flatpak-utils.c @@ -592,6 +592,9 @@ flatpak_get_gtk_theme (void) gboolean flatpak_fancy_output (void) { + if (g_strcmp0 (g_getenv ("FLATPAK_FANCY_OUTPUT"), "0") == 0) + return FALSE; + return isatty (STDOUT_FILENO); }