1 /*
2  * Copyright (C) 2008 Red Hat, Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General
15  * Public License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
17  * Boston, MA 02111-1307, USA.
18  *
19  * Author: David Zeuthen <davidz@redhat.com>
20  */
21 
22 #include "config.h"
23 
24 #include <errno.h>
25 #include <string.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <sys/types.h>
29 #include <pwd.h>
30 
31 #include <termios.h>
32 #include <unistd.h>
33 
34 #include <polkit/polkitprivate.h>
35 
36 #include "polkitagentlistener.h"
37 #include "polkitagenttextlistener.h"
38 #include "polkitagentsession.h"
39 
40 /**
41  * SECTION:polkitagenttextlistener
42  * @title: PolkitAgentTextListener
43  * @short_description: Text-based Authentication Agent
44  * @stability: Unstable
45  *
46  * #PolkitAgentTextListener is an #PolkitAgentListener implementation
47  * that interacts with the user using a textual interface.
48  */
49 
50 /**
51  * PolkitAgentTextListener:
52  *
53  * The #PolkitAgentTextListener struct should not be accessed directly.
54  */
55 struct _PolkitAgentTextListener
56 {
57   PolkitAgentListener parent_instance;
58 
59   GSimpleAsyncResult *simple;
60   PolkitAgentSession *active_session;
61   gulong cancel_id;
62   GCancellable *cancellable;
63 
64   FILE *tty;
65 
66   gboolean use_color;
67   gboolean use_alternate_buffer;
68   guint delay;
69 };
70 
71 enum {
72   PROP_ZERO,
73   PROP_USE_COLOR,
74   PROP_USE_ALTERNATE_BUFFER,
75   PROP_DELAY
76 };
77 
78 typedef struct
79 {
80   PolkitAgentListenerClass parent_class;
81 } PolkitAgentTextListenerClass;
82 
83 static void polkit_agent_text_listener_initiate_authentication (PolkitAgentListener  *_listener,
84                                                                 const gchar          *action_id,
85                                                                 const gchar          *message,
86                                                                 const gchar          *icon_name,
87                                                                 PolkitDetails        *details,
88                                                                 const gchar          *cookie,
89                                                                 GList                *identities,
90                                                                 GCancellable         *cancellable,
91                                                                 GAsyncReadyCallback   callback,
92                                                                 gpointer              user_data);
93 
94 static gboolean polkit_agent_text_listener_initiate_authentication_finish (PolkitAgentListener  *_listener,
95                                                                            GAsyncResult         *res,
96                                                                            GError              **error);
97 
98 static void initable_iface_init (GInitableIface *initable_iface);
99 
100 G_DEFINE_TYPE_WITH_CODE (PolkitAgentTextListener, polkit_agent_text_listener, POLKIT_AGENT_TYPE_LISTENER,
101                          G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init));
102 
103 static void
polkit_agent_text_listener_init(PolkitAgentTextListener * listener)104 polkit_agent_text_listener_init (PolkitAgentTextListener *listener)
105 {
106   listener->use_color = TRUE;
107   listener->use_alternate_buffer = FALSE;
108   listener->delay = 1;
109 }
110 
111 static void
polkit_agent_text_listener_finalize(GObject * object)112 polkit_agent_text_listener_finalize (GObject *object)
113 {
114   PolkitAgentTextListener *listener = POLKIT_AGENT_TEXT_LISTENER (object);
115 
116   if (listener->tty != NULL)
117     fclose (listener->tty);
118 
119   if (listener->active_session != NULL)
120     g_object_unref (listener->active_session);
121 
122   if (G_OBJECT_CLASS (polkit_agent_text_listener_parent_class)->finalize != NULL)
123     G_OBJECT_CLASS (polkit_agent_text_listener_parent_class)->finalize (object);
124 }
125 
126 static void
polkit_agent_text_listener_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)127 polkit_agent_text_listener_set_property (GObject      *object,
128                                          guint         prop_id,
129                                          const GValue *value,
130                                          GParamSpec   *pspec)
131 {
132   PolkitAgentTextListener *listener = POLKIT_AGENT_TEXT_LISTENER (object);
133 
134   switch (prop_id)
135     {
136     case PROP_USE_COLOR:
137       listener->use_color = g_value_get_boolean (value);
138       break;
139     case PROP_USE_ALTERNATE_BUFFER:
140       listener->use_alternate_buffer = g_value_get_boolean (value);
141       break;
142     case PROP_DELAY:
143       listener->delay = g_value_get_uint (value);
144       break;
145     default:
146       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
147       break;
148     }
149 }
150 
151 static void
polkit_agent_text_listener_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)152 polkit_agent_text_listener_get_property (GObject    *object,
153                                          guint       prop_id,
154                                          GValue     *value,
155                                          GParamSpec *pspec)
156 {
157   PolkitAgentTextListener *listener = POLKIT_AGENT_TEXT_LISTENER (object);
158 
159   switch (prop_id)
160     {
161     case PROP_USE_COLOR:
162       g_value_set_boolean (value, listener->use_color);
163       break;
164     case PROP_USE_ALTERNATE_BUFFER:
165       g_value_set_boolean (value, listener->use_alternate_buffer);
166       break;
167     case PROP_DELAY:
168       g_value_set_uint (value, listener->delay);
169       break;
170     default:
171       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
172       break;
173     }
174 }
175 
176 static void
polkit_agent_text_listener_class_init(PolkitAgentTextListenerClass * klass)177 polkit_agent_text_listener_class_init (PolkitAgentTextListenerClass *klass)
178 {
179   GObjectClass *gobject_class;
180   PolkitAgentListenerClass *listener_class;
181 
182   gobject_class = G_OBJECT_CLASS (klass);
183   gobject_class->finalize = polkit_agent_text_listener_finalize;
184   gobject_class->get_property = polkit_agent_text_listener_get_property;
185   gobject_class->set_property = polkit_agent_text_listener_set_property;
186 
187   listener_class = POLKIT_AGENT_LISTENER_CLASS (klass);
188   listener_class->initiate_authentication        = polkit_agent_text_listener_initiate_authentication;
189   listener_class->initiate_authentication_finish = polkit_agent_text_listener_initiate_authentication_finish;
190 
191   g_object_class_install_property (gobject_class,
192                                    PROP_USE_COLOR,
193                                    g_param_spec_boolean ("use-color", "", "",
194                                                          TRUE,
195                                                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
196 
197   g_object_class_install_property (gobject_class,
198                                    PROP_USE_ALTERNATE_BUFFER,
199                                    g_param_spec_boolean ("use-alternate-buffer", "", "",
200                                                          FALSE,
201                                                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
202 
203   g_object_class_install_property (gobject_class,
204                                    PROP_DELAY,
205                                    g_param_spec_uint ("delay", "", "",
206                                                       0, G_MAXUINT, 1,
207                                                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
208 }
209 
210 /**
211  * polkit_agent_text_listener_new:
212  * @cancellable: A #GCancellable or %NULL.
213  * @error: Return location for error or %NULL.
214  *
215  * Creates a new #PolkitAgentTextListener for authenticating the user
216  * via an textual interface on the controlling terminal
217  * (e.g. <filename>/dev/tty</filename>). This can fail if e.g. the
218  * current process has no controlling terminal.
219  *
220  * Returns: A #PolkitAgentTextListener or %NULL if @error is set. Free with g_object_unref() when done with it.
221  */
222 PolkitAgentListener *
polkit_agent_text_listener_new(GCancellable * cancellable,GError ** error)223 polkit_agent_text_listener_new (GCancellable  *cancellable,
224                                 GError       **error)
225 {
226   g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
227   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
228   return POLKIT_AGENT_LISTENER (g_initable_new (POLKIT_AGENT_TYPE_TEXT_LISTENER,
229                                                 cancellable,
230                                                 error,
231                                                 NULL));
232 }
233 
234 /* ---------------------------------------------------------------------------------------------------- */
235 
236 static gboolean
initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)237 initable_init (GInitable     *initable,
238                GCancellable  *cancellable,
239                GError       **error)
240 {
241   PolkitAgentTextListener *listener = POLKIT_AGENT_TEXT_LISTENER (initable);
242   gboolean ret;
243   const gchar *tty_name;
244 
245   ret = FALSE;
246 
247   tty_name = ctermid (NULL);
248   if (tty_name == NULL)
249     {
250       g_set_error (error,
251                    POLKIT_ERROR,
252                    POLKIT_ERROR_FAILED,
253                    "Cannot determine pathname for current controlling terminal for the process: %s",
254                    strerror (errno));
255       goto out;
256     }
257 
258   listener->tty = fopen (tty_name, "r+");
259   if (listener->tty == NULL)
260     {
261       g_set_error (error,
262                    POLKIT_ERROR,
263                    POLKIT_ERROR_FAILED,
264                    "Error opening current controlling terminal for the process (`%s'): %s",
265                    tty_name,
266                    strerror (errno));
267       goto out;
268     }
269 
270   ret = TRUE;
271 
272  out:
273   return ret;
274 }
275 
276 static void
initable_iface_init(GInitableIface * initable_iface)277 initable_iface_init (GInitableIface *initable_iface)
278 {
279   initable_iface->init = initable_init;
280 }
281 
282 /* ---------------------------------------------------------------------------------------------------- */
283 
284 static void
on_completed(PolkitAgentSession * session,gboolean gained_authorization,gpointer user_data)285 on_completed (PolkitAgentSession *session,
286               gboolean            gained_authorization,
287               gpointer            user_data)
288 {
289   PolkitAgentTextListener *listener = POLKIT_AGENT_TEXT_LISTENER (user_data);
290 
291   if (listener->use_color)
292     fprintf (listener->tty, "\x1B[1;31m");
293   if (gained_authorization)
294     fprintf (listener->tty, "==== AUTHENTICATION COMPLETE ====\n");
295   else
296     fprintf (listener->tty, "==== AUTHENTICATION FAILED ====\n");
297   if (listener->use_color)
298     fprintf (listener->tty, "\x1B[0m");
299   if (listener->use_alternate_buffer)
300     {
301       sleep (listener->delay);
302       fprintf (listener->tty, "\x1B[?1049l");
303     }
304   fflush (listener->tty);
305 
306   g_simple_async_result_complete_in_idle (listener->simple);
307 
308   g_object_unref (listener->simple);
309   g_object_unref (listener->active_session);
310   g_cancellable_disconnect (listener->cancellable, listener->cancel_id);
311   g_object_unref (listener->cancellable);
312 
313   listener->simple = NULL;
314   listener->active_session = NULL;
315   listener->cancel_id = 0;
316 }
317 
318 static void
on_request(PolkitAgentSession * session,const gchar * request,gboolean echo_on,gpointer user_data)319 on_request (PolkitAgentSession *session,
320             const gchar        *request,
321             gboolean            echo_on,
322             gpointer            user_data)
323 {
324   PolkitAgentTextListener *listener = POLKIT_AGENT_TEXT_LISTENER (user_data);
325   struct termios ts, ots;
326   GString *str;
327 
328   fprintf (listener->tty, "%s", request);
329   fflush (listener->tty);
330 
331   setbuf (listener->tty, NULL);
332 
333   /* TODO: We really ought to block SIGINT and STGSTP (and probably
334    *       other signals too) so we can restore the terminal (since we
335    *       turn off echoing). See e.g. Advanced Programming in the
336    *       UNIX Environment 2nd edition (Steves and Rago) section
337    *       18.10, pg 660 where this is suggested. See also various
338    *       getpass(3) implementations
339    *
340    *       However, since we are a library routine the user could have
341    *       multiple threads - in fact, typical usage of
342    *       PolkitAgentTextListener is to run it in a thread. And
343    *       unfortunately threads and POSIX signals is a royal PITA.
344    *
345    *       Maybe we could fork(2) and ask for the password in the
346    *       child and send it back to the parent over a pipe? (we are
347    *       guaranteed that there is only one thread in the child
348    *       process).
349    *
350    *       (Side benefit of doing this in a child process is that we
351    *       could avoid blocking the thread where the
352    *       PolkitAgentTextListener object is being serviced from. But
353    *       since this class is normally used in a dedicated thread
354    *       it doesn't really matter *anyway*.)
355    *
356    *       Anyway, On modern Linux not doing this doesn't seem to be a
357    *       problem - looks like modern shells restore echoing anyway
358    *       on the first input. So maybe it's not even worth solving
359    *       the problem.
360    */
361 
362   tcgetattr (fileno (listener->tty), &ts);
363   ots = ts;
364   ts.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
365   tcsetattr (fileno (listener->tty), TCSAFLUSH, &ts);
366 
367   str = g_string_new (NULL);
368   while (TRUE)
369     {
370       gint c;
371       c = getc (listener->tty);
372       if (c == '\n')
373         {
374           /* ok, done */
375           break;
376         }
377       else if (c == EOF)
378         {
379           tcsetattr (fileno (listener->tty), TCSAFLUSH, &ots);
380           g_error ("Got unexpected EOF while reading from controlling terminal.");
381           abort ();
382           break;
383         }
384       else
385         {
386           g_string_append_c (str, c);
387         }
388     }
389   tcsetattr (fileno (listener->tty), TCSAFLUSH, &ots);
390   putc ('\n', listener->tty);
391 
392   polkit_agent_session_response (session, str->str);
393   memset (str->str, '\0', str->len);
394   g_string_free (str, TRUE);
395 }
396 
397 static void
on_show_error(PolkitAgentSession * session,const gchar * text,gpointer user_data)398 on_show_error (PolkitAgentSession *session,
399                const gchar        *text,
400                gpointer            user_data)
401 {
402   PolkitAgentTextListener *listener = POLKIT_AGENT_TEXT_LISTENER (user_data);
403   fprintf (listener->tty, "Error: %s\n", text);
404   fflush (listener->tty);
405 }
406 
407 static void
on_show_info(PolkitAgentSession * session,const gchar * text,gpointer user_data)408 on_show_info (PolkitAgentSession *session,
409               const gchar        *text,
410               gpointer            user_data)
411 {
412   PolkitAgentTextListener *listener = POLKIT_AGENT_TEXT_LISTENER (user_data);
413   fprintf (listener->tty, "Info: %s\n", text);
414   fflush (listener->tty);
415 }
416 
417 static void
on_cancelled(GCancellable * cancellable,gpointer user_data)418 on_cancelled (GCancellable *cancellable,
419               gpointer      user_data)
420 {
421   PolkitAgentTextListener *listener = POLKIT_AGENT_TEXT_LISTENER (user_data);
422   fprintf (listener->tty, "Cancelled\n");
423   fflush (listener->tty);
424   polkit_agent_session_cancel (listener->active_session);
425 }
426 
427 static gchar *
identity_to_human_readable_string(PolkitIdentity * identity)428 identity_to_human_readable_string (PolkitIdentity *identity)
429 {
430   gchar *ret;
431 
432   g_return_val_if_fail (POLKIT_IS_IDENTITY (identity), NULL);
433 
434   ret = NULL;
435   if (POLKIT_IS_UNIX_USER (identity))
436     {
437       struct passwd pw;
438       struct passwd *ppw;
439       char buf[2048];
440       int res;
441 
442       res = getpwuid_r (polkit_unix_user_get_uid (POLKIT_UNIX_USER (identity)),
443                         &pw,
444                         buf,
445                         sizeof buf,
446                         &ppw);
447       if (res != 0)
448         {
449           g_warning ("Error calling getpwuid_r: %s", strerror (res));
450         }
451       else
452         {
453           if (ppw->pw_gecos == NULL || strlen (ppw->pw_gecos) == 0 || strcmp (ppw->pw_gecos, ppw->pw_name) == 0)
454             {
455               ret = g_strdup_printf ("%s", ppw->pw_name);
456             }
457           else
458             {
459               ret = g_strdup_printf ("%s (%s)", ppw->pw_gecos, ppw->pw_name);
460             }
461         }
462     }
463   if (ret == NULL)
464     ret = polkit_identity_to_string (identity);
465   return ret;
466 }
467 
468 static PolkitIdentity *
choose_identity(PolkitAgentTextListener * listener,GList * identities)469 choose_identity (PolkitAgentTextListener *listener,
470                  GList                   *identities)
471 {
472   GList *l;
473   guint n;
474   guint num_identities;
475   GString *str;
476   PolkitIdentity *ret;
477   guint num;
478   gchar *endp;
479 
480   ret = NULL;
481 
482   fprintf (listener->tty, "Multiple identities can be used for authentication:\n");
483   for (l = identities, n = 0; l != NULL; l = l->next, n++)
484     {
485       PolkitIdentity *identity = POLKIT_IDENTITY (l->data);
486       gchar *s;
487       s = identity_to_human_readable_string (identity);
488       fprintf (listener->tty, " %d.  %s\n", n + 1, s);
489       g_free (s);
490     }
491   num_identities = n;
492   fprintf (listener->tty, "Choose identity to authenticate as (1-%d): ", num_identities);
493   fflush (listener->tty);
494 
495   str = g_string_new (NULL);
496   while (TRUE)
497     {
498       gint c;
499       c = getc (listener->tty);
500       if (c == '\n')
501         {
502           /* ok, done */
503           break;
504         }
505       else if (c == EOF)
506         {
507           g_error ("Got unexpected EOF while reading from controlling terminal.");
508           abort ();
509           break;
510         }
511       else
512         {
513           g_string_append_c (str, c);
514         }
515     }
516 
517   num = strtol (str->str, &endp, 10);
518   if (str->len == 0 || *endp != '\0' || (num < 1 || num > num_identities))
519     {
520       fprintf (listener->tty, "Invalid response `%s'.\n", str->str);
521       goto out;
522     }
523 
524   ret = g_list_nth_data (identities, num-1);
525 
526  out:
527   g_string_free (str, TRUE);
528   return ret;
529 }
530 
531 
532 static void
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)533 polkit_agent_text_listener_initiate_authentication (PolkitAgentListener  *_listener,
534                                                     const gchar          *action_id,
535                                                     const gchar          *message,
536                                                     const gchar          *icon_name,
537                                                     PolkitDetails        *details,
538                                                     const gchar          *cookie,
539                                                     GList                *identities,
540                                                     GCancellable         *cancellable,
541                                                     GAsyncReadyCallback   callback,
542                                                     gpointer              user_data)
543 {
544   PolkitAgentTextListener *listener = POLKIT_AGENT_TEXT_LISTENER (_listener);
545   GSimpleAsyncResult *simple;
546   PolkitIdentity *identity;
547 
548   simple = g_simple_async_result_new (G_OBJECT (listener),
549                                       callback,
550                                       user_data,
551                                       polkit_agent_text_listener_initiate_authentication);
552   if (listener->active_session != NULL)
553     {
554       g_simple_async_result_set_error (simple,
555                                        POLKIT_ERROR,
556                                        POLKIT_ERROR_FAILED,
557                                        "An authentication session is already underway.");
558       g_simple_async_result_complete_in_idle (simple);
559       g_object_unref (simple);
560       goto out;
561     }
562 
563   g_assert (g_list_length (identities) >= 1);
564 
565   if (listener->use_alternate_buffer)
566     fprintf (listener->tty, "\x1B[?1049h");
567   if (listener->use_color)
568     fprintf (listener->tty, "\x1B[1;31m");
569   fprintf (listener->tty,
570            "==== AUTHENTICATING FOR %s ====\n",
571            action_id);
572   if (listener->use_color)
573     fprintf (listener->tty, "\x1B[0m");
574   fprintf (listener->tty,
575            "%s\n",
576            message);
577 
578   /* handle multiple identies by asking which one to use */
579   if (g_list_length (identities) > 1)
580     {
581       identity = choose_identity (listener, identities);
582       if (identity == NULL)
583         {
584           if (listener->use_color)
585             fprintf (listener->tty, "\x1B[1;31m");
586           fprintf (listener->tty, "==== AUTHENTICATION CANCELED ====\n");
587           if (listener->use_color)
588             fprintf (listener->tty, "\x1B[0m");
589           fflush (listener->tty);
590           g_simple_async_result_set_error (simple,
591                                            POLKIT_ERROR,
592                                            POLKIT_ERROR_FAILED,
593                                            "Authentication was canceled.");
594           g_simple_async_result_complete_in_idle (simple);
595           g_object_unref (simple);
596           goto out;
597         }
598     }
599   else
600     {
601       gchar *s;
602       identity = identities->data;
603       s = identity_to_human_readable_string (identity);
604       fprintf (listener->tty,
605                "Authenticating as: %s\n",
606                s);
607       g_free (s);
608     }
609 
610   listener->active_session = polkit_agent_session_new (identity, cookie);
611   g_signal_connect (listener->active_session,
612                     "completed",
613                     G_CALLBACK (on_completed),
614                     listener);
615   g_signal_connect (listener->active_session,
616                     "request",
617                     G_CALLBACK (on_request),
618                     listener);
619   g_signal_connect (listener->active_session,
620                     "show-info",
621                     G_CALLBACK (on_show_info),
622                     listener);
623   g_signal_connect (listener->active_session,
624                     "show-error",
625                     G_CALLBACK (on_show_error),
626                     listener);
627 
628   listener->simple = simple;
629   listener->cancellable = g_object_ref (cancellable);
630   listener->cancel_id = g_cancellable_connect (cancellable,
631                                                G_CALLBACK (on_cancelled),
632                                                listener,
633                                                NULL);
634 
635   polkit_agent_session_initiate (listener->active_session);
636 
637  out:
638   ;
639 }
640 
641 static gboolean
polkit_agent_text_listener_initiate_authentication_finish(PolkitAgentListener * _listener,GAsyncResult * res,GError ** error)642 polkit_agent_text_listener_initiate_authentication_finish (PolkitAgentListener  *_listener,
643                                                            GAsyncResult         *res,
644                                                            GError              **error)
645 {
646   gboolean ret;
647 
648   g_warn_if_fail (g_simple_async_result_get_source_tag (G_SIMPLE_ASYNC_RESULT (res)) ==
649                   polkit_agent_text_listener_initiate_authentication);
650 
651   ret = FALSE;
652 
653   if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
654     goto out;
655 
656   ret = TRUE;
657 
658  out:
659   return ret;
660 }
661