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