1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2006 William Jon McCann <mccann@jhu.edu>
4 * Copyright (C) 2006 Ray Strode <rstrode@redhat.com>
5 * Copyright (C) 2003 Bill Nottingham <notting@redhat.com>
6 * Copyright (c) 1993-2003 Jamie Zawinski <jwz@jwz.org>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street - Suite 500, Boston, MA
21 * 02110-1335, USA.
22 *
23 */
24
25 #include "config.h"
26
27 #include <stdlib.h>
28 #ifdef HAVE_UNISTD_H
29 # include <unistd.h>
30 #endif
31
32 #include <fcntl.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <sys/types.h>
36 #include <pwd.h>
37 #include <grp.h>
38 #include <security/pam_appl.h>
39 #include <signal.h>
40 #include <errno.h>
41
42 #include <glib.h>
43 #include <glib/gstdio.h>
44 #include <glib/gi18n-lib.h>
45 #include <gtk/gtk.h>
46
47 #include "cs-auth.h"
48
49 #include "subprocs.h"
50
51 /* Some time between Red Hat 4.2 and 7.0, the words were transposed
52 in the various PAM_x_CRED macro names. Yay!
53 */
54 #ifndef PAM_REFRESH_CRED
55 # define PAM_REFRESH_CRED PAM_CRED_REFRESH
56 #endif
57
58 #ifdef HAVE_PAM_FAIL_DELAY
59 /* We handle delays ourself.*/
60 /* Don't set this to 0 (Linux bug workaround.) */
61 # define PAM_NO_DELAY(pamh) pam_fail_delay ((pamh), 1)
62 #else /* !HAVE_PAM_FAIL_DELAY */
63 # define PAM_NO_DELAY(pamh) /* */
64 #endif /* !HAVE_PAM_FAIL_DELAY */
65
66
67 /* On SunOS 5.6, and on Linux with PAM 0.64, pam_strerror() takes two args.
68 On some other Linux systems with some other version of PAM (e.g.,
69 whichever Debian release comes with a 2.2.5 kernel) it takes one arg.
70 I can't tell which is more "recent" or "correct" behavior, so configure
71 figures out which is in use for us. Shoot me!
72 */
73 #ifdef PAM_STRERROR_TWO_ARGS
74 # define PAM_STRERROR(pamh, status) pam_strerror((pamh), (status))
75 #else /* !PAM_STRERROR_TWO_ARGS */
76 # define PAM_STRERROR(pamh, status) pam_strerror((status))
77 #endif /* !PAM_STRERROR_TWO_ARGS */
78
79 static gboolean verbose_enabled = FALSE;
80 static pam_handle_t *pam_handle = NULL;
81 static gboolean did_we_ask_for_password = FALSE;
82
83 #define DEBUG(...) if (verbose_enabled) g_printerr (__VA_ARGS__)
84
85 struct pam_closure {
86 const char *username;
87 CsAuthMessageFunc cb_func;
88 gpointer cb_data;
89 int signal_fd;
90 int result;
91 };
92
93 typedef struct {
94 struct pam_closure *closure;
95 CsAuthMessageStyle style;
96 const char *msg;
97 char **resp;
98 gboolean should_interrupt_stack;
99 } GsAuthMessageHandlerData;
100
101 static GCond *message_handled_condition;
102 static GMutex *message_handler_mutex;
103
104 GQuark
cs_auth_error_quark(void)105 cs_auth_error_quark (void)
106 {
107 static GQuark quark = 0;
108 if (! quark) {
109 quark = g_quark_from_static_string ("cs_auth_error");
110 }
111
112 return quark;
113 }
114
115 void
cs_auth_set_verbose(gboolean enabled)116 cs_auth_set_verbose (gboolean enabled)
117 {
118 verbose_enabled = enabled;
119 }
120
121 gboolean
cs_auth_get_verbose(void)122 cs_auth_get_verbose (void)
123 {
124 return verbose_enabled;
125 }
126
127 static CsAuthMessageStyle
pam_style_to_cs_style(int pam_style)128 pam_style_to_cs_style (int pam_style)
129 {
130 CsAuthMessageStyle style;
131
132 switch (pam_style) {
133 case PAM_PROMPT_ECHO_ON:
134 style = CS_AUTH_MESSAGE_PROMPT_ECHO_ON;
135 break;
136 case PAM_PROMPT_ECHO_OFF:
137 style = CS_AUTH_MESSAGE_PROMPT_ECHO_OFF;
138 break;
139 case PAM_ERROR_MSG:
140 style = CS_AUTH_MESSAGE_ERROR_MSG;
141 break;
142 case PAM_TEXT_INFO:
143 style = CS_AUTH_MESSAGE_TEXT_INFO;
144 break;
145 default:
146 g_assert_not_reached ();
147 break;
148 }
149
150 return style;
151 }
152
153 static gboolean
auth_message_handler(CsAuthMessageStyle style,const char * msg,char ** response,gpointer data)154 auth_message_handler (CsAuthMessageStyle style,
155 const char *msg,
156 char **response,
157 gpointer data)
158 {
159 gboolean ret;
160
161 ret = TRUE;
162 *response = NULL;
163
164 switch (style) {
165 case CS_AUTH_MESSAGE_PROMPT_ECHO_ON:
166 break;
167 case CS_AUTH_MESSAGE_PROMPT_ECHO_OFF:
168 if (msg != NULL && g_str_has_prefix (msg, _("Password:"))) {
169 did_we_ask_for_password = TRUE;
170 }
171 break;
172 case CS_AUTH_MESSAGE_ERROR_MSG:
173 break;
174 case CS_AUTH_MESSAGE_TEXT_INFO:
175 break;
176 default:
177 g_assert_not_reached ();
178 }
179
180 return ret;
181 }
182
183 static gboolean
cs_auth_queued_message_handler(GsAuthMessageHandlerData * data)184 cs_auth_queued_message_handler (GsAuthMessageHandlerData *data)
185 {
186 gboolean res;
187
188 if (cs_auth_get_verbose ()) {
189 DEBUG ("Waiting for lock\n");
190 }
191
192 g_mutex_lock (message_handler_mutex);
193
194 if (cs_auth_get_verbose ()) {
195 DEBUG ("Waiting for response\n");
196 }
197
198 res = data->closure->cb_func (data->style,
199 data->msg,
200 data->resp,
201 data->closure->cb_data);
202 data->should_interrupt_stack = res == FALSE;
203
204 g_printerr ("should interrupt: %d\n", data->should_interrupt_stack);
205
206 g_cond_signal (message_handled_condition);
207 g_mutex_unlock (message_handler_mutex);
208
209 if (cs_auth_get_verbose ()) {
210 DEBUG ("Got response\n");
211 }
212
213 return FALSE;
214 }
215
216 static gboolean
cs_auth_run_message_handler(struct pam_closure * c,CsAuthMessageStyle style,const char * msg,char ** resp)217 cs_auth_run_message_handler (struct pam_closure *c,
218 CsAuthMessageStyle style,
219 const char *msg,
220 char **resp)
221 {
222 GsAuthMessageHandlerData data;
223
224 data.closure = c;
225 data.style = style;
226 data.msg = msg;
227 data.resp = resp;
228 data.should_interrupt_stack = TRUE;
229
230 g_mutex_lock (message_handler_mutex);
231
232 /* Queue the callback in the gui (the main) thread
233 */
234 g_idle_add ((GSourceFunc) cs_auth_queued_message_handler, &data);
235
236 if (cs_auth_get_verbose ()) {
237 DEBUG ("Waiting for respose to message style %d: '%s'\n", style, msg);
238 }
239
240 /* Wait for the response
241 */
242 g_cond_wait (message_handled_condition,
243 message_handler_mutex);
244 g_mutex_unlock (message_handler_mutex);
245
246 if (cs_auth_get_verbose ()) {
247 DEBUG ("Got respose to message style %d: interrupt:%d\n", style, data.should_interrupt_stack);
248 }
249
250 return data.should_interrupt_stack == FALSE;
251 }
252
253 static int
pam_conversation(int nmsgs,const struct pam_message ** msg,struct pam_response ** resp,void * closure)254 pam_conversation (int nmsgs,
255 const struct pam_message **msg,
256 struct pam_response **resp,
257 void *closure)
258 {
259 int replies = 0;
260 struct pam_response *reply = NULL;
261 struct pam_closure *c = (struct pam_closure *) closure;
262 gboolean res;
263 int ret;
264
265 reply = (struct pam_response *) calloc (nmsgs, sizeof (*reply));
266
267 if (reply == NULL) {
268 return PAM_CONV_ERR;
269 }
270
271 res = TRUE;
272 ret = PAM_SUCCESS;
273
274 for (replies = 0; replies < nmsgs && ret == PAM_SUCCESS; replies++) {
275 CsAuthMessageStyle style;
276 char *utf8_msg;
277
278 style = pam_style_to_cs_style (msg [replies]->msg_style);
279
280 utf8_msg = g_locale_to_utf8 (msg [replies]->msg,
281 -1,
282 NULL,
283 NULL,
284 NULL);
285
286 /* if we couldn't convert text from locale then
287 * assume utf-8 and hope for the best */
288 if (utf8_msg == NULL) {
289 char *p;
290 char *q;
291
292 utf8_msg = g_strdup (msg [replies]->msg);
293
294 p = utf8_msg;
295 while (*p != '\0' && !g_utf8_validate ((const char *)p, -1, (const char **)&q)) {
296 *q = '?';
297 p = q + 1;
298 }
299 }
300
301 /* handle message locally first */
302 auth_message_handler (style,
303 utf8_msg,
304 &reply [replies].resp,
305 NULL);
306
307 if (c->cb_func != NULL) {
308 if (cs_auth_get_verbose ()) {
309 DEBUG ("Handling message style %d: '%s'\n", style, utf8_msg);
310 }
311
312 /* blocks until the gui responds
313 */
314 res = cs_auth_run_message_handler (c,
315 style,
316 utf8_msg,
317 &reply [replies].resp);
318
319 if (cs_auth_get_verbose ()) {
320 DEBUG ("Msg handler returned %d\n", res);
321 }
322
323 /* If the handler returns FALSE - interrupt the PAM stack */
324 if (res) {
325 reply [replies].resp_retcode = PAM_SUCCESS;
326 } else {
327 int i;
328 for (i = 0; i <= replies; i++) {
329 free (reply [i].resp);
330 }
331 free (reply);
332 reply = NULL;
333 ret = PAM_CONV_ERR;
334 }
335 }
336
337 g_free (utf8_msg);
338 }
339
340 *resp = reply;
341
342 return ret;
343 }
344
345 static gboolean
close_pam_handle(int status)346 close_pam_handle (int status)
347 {
348
349 if (pam_handle != NULL) {
350 int status2;
351
352 status2 = pam_end (pam_handle, status);
353 pam_handle = NULL;
354
355 if (cs_auth_get_verbose ()) {
356 DEBUG (" pam_end (...) ==> %d (%s)\n",
357 status2,
358 (status2 == PAM_SUCCESS ? "Success" : "Failure"));
359 }
360 }
361
362 if (message_handled_condition != NULL) {
363 g_cond_free (message_handled_condition);
364 message_handled_condition = NULL;
365 }
366
367 if (message_handler_mutex != NULL) {
368 g_mutex_free (message_handler_mutex);
369 message_handler_mutex = NULL;
370 }
371
372 return TRUE;
373 }
374
375 static gboolean
create_pam_handle(const char * username,const char * display,struct pam_conv * conv,int * status_code)376 create_pam_handle (const char *username,
377 const char *display,
378 struct pam_conv *conv,
379 int *status_code)
380 {
381 int status;
382 const char *service = PAM_SERVICE_NAME;
383 char *disp;
384 gboolean ret;
385
386 if (pam_handle != NULL) {
387 g_warning ("create_pam_handle: Stale pam handle around, cleaning up\n");
388 close_pam_handle (PAM_SUCCESS);
389 }
390
391 /* init things */
392 pam_handle = NULL;
393 status = -1;
394 disp = NULL;
395 ret = TRUE;
396
397 /* Initialize a PAM session for the user */
398 if ((status = pam_start (service, username, conv, &pam_handle)) != PAM_SUCCESS) {
399 pam_handle = NULL;
400 g_warning (_("Unable to establish service %s: %s\n"),
401 service,
402 PAM_STRERROR (NULL, status));
403
404 if (status_code != NULL) {
405 *status_code = status;
406 }
407
408 ret = FALSE;
409 goto out;
410 }
411
412 if (cs_auth_get_verbose ()) {
413 DEBUG ("pam_start (\"%s\", \"%s\", ...) ==> %d (%s)\n",
414 service,
415 username,
416 status,
417 PAM_STRERROR (pam_handle, status));
418 }
419
420 disp = g_strdup (display);
421 if (disp == NULL) {
422 disp = g_strdup (":0.0");
423 }
424
425 if ((status = pam_set_item (pam_handle, PAM_TTY, disp)) != PAM_SUCCESS) {
426 g_warning (_("Can't set PAM_TTY=%s"), display);
427
428 if (status_code != NULL) {
429 *status_code = status;
430 }
431
432 ret = FALSE;
433 goto out;
434 }
435
436 ret = TRUE;
437 message_handled_condition = g_cond_new ();
438 message_handler_mutex = g_mutex_new ();
439
440 out:
441 if (status_code != NULL) {
442 *status_code = status;
443 }
444
445 g_free (disp);
446
447 return ret;
448 }
449
450 static void
set_pam_error(GError ** error,int status)451 set_pam_error (GError **error,
452 int status)
453 {
454 if (status == PAM_AUTH_ERR || status == PAM_USER_UNKNOWN) {
455 char *msg;
456
457 if (did_we_ask_for_password) {
458 msg = g_strdup (_("Incorrect password."));
459 } else {
460 msg = g_strdup (_("Authentication failed."));
461 }
462
463 g_set_error (error,
464 CS_AUTH_ERROR,
465 CS_AUTH_ERROR_AUTH_ERROR,
466 "%s",
467 msg);
468 g_free (msg);
469 } else if (status == PAM_PERM_DENIED) {
470 g_set_error (error,
471 CS_AUTH_ERROR,
472 CS_AUTH_ERROR_AUTH_DENIED,
473 "%s",
474 _("Not permitted to gain access at this time."));
475 } else if (status == PAM_ACCT_EXPIRED) {
476 g_set_error (error,
477 CS_AUTH_ERROR,
478 CS_AUTH_ERROR_AUTH_DENIED,
479 "%s",
480 _("No longer permitted to access the system."));
481 }
482
483 }
484
485 static int
cs_auth_thread_func(int auth_operation_fd)486 cs_auth_thread_func (int auth_operation_fd)
487 {
488 static const int flags = 0;
489 int status;
490 int status2;
491 struct timespec timeout;
492 sigset_t set;
493 const void *p;
494
495 timeout.tv_sec = 0;
496 timeout.tv_nsec = 1;
497
498 set = block_sigchld ();
499
500 status = pam_authenticate (pam_handle, flags);
501
502 sigtimedwait (&set, NULL, &timeout);
503 unblock_sigchld ();
504
505 if (cs_auth_get_verbose ()) {
506 DEBUG (" pam_authenticate (...) ==> %d (%s)\n",
507 status,
508 PAM_STRERROR (pam_handle, status));
509 }
510
511 if (status != PAM_SUCCESS) {
512 goto done;
513 }
514
515 if ((status = pam_get_item (pam_handle, PAM_USER, &p)) != PAM_SUCCESS) {
516 /* is not really an auth problem, but it will
517 pretty much look as such, it shouldn't really
518 happen */
519 goto done;
520 }
521
522 /* We don't actually care if the account modules fail or succeed,
523 * but we need to run them anyway because certain pam modules
524 * depend on side effects of the account modules getting run.
525 */
526 status2 = pam_acct_mgmt (pam_handle, 0);
527
528 if (cs_auth_get_verbose ()) {
529 DEBUG ("pam_acct_mgmt (...) ==> %d (%s)\n",
530 status2,
531 PAM_STRERROR (pam_handle, status2));
532 }
533
534 /* FIXME: should we handle these? */
535 switch (status2) {
536 case PAM_SUCCESS:
537 break;
538 case PAM_NEW_AUTHTOK_REQD:
539 break;
540 case PAM_AUTHINFO_UNAVAIL:
541 break;
542 case PAM_ACCT_EXPIRED:
543 break;
544 case PAM_PERM_DENIED:
545 break;
546 default :
547 break;
548 }
549
550 /* Each time we successfully authenticate, refresh credentials,
551 for Kerberos/AFS/DCE/etc. If this fails, just ignore that
552 failure and blunder along; it shouldn't matter.
553
554 Note: this used to be PAM_REFRESH_CRED instead of
555 PAM_REINITIALIZE_CRED, but Jason Heiss <jheiss@ee.washington.edu>
556 says that the Linux PAM library ignores that one, and only refreshes
557 credentials when using PAM_REINITIALIZE_CRED.
558 */
559 status2 = pam_setcred (pam_handle, PAM_REINITIALIZE_CRED);
560 if (cs_auth_get_verbose ()) {
561 DEBUG (" pam_setcred (...) ==> %d (%s)\n",
562 status2,
563 PAM_STRERROR (pam_handle, status2));
564 }
565
566 done:
567 /* we're done, close the fd and wake up the main
568 * loop
569 */
570 close (auth_operation_fd);
571
572 return status;
573 }
574
575 static gboolean
cs_auth_loop_quit(GIOChannel * source,GIOCondition condition,gboolean * thread_done)576 cs_auth_loop_quit (GIOChannel *source,
577 GIOCondition condition,
578 gboolean *thread_done)
579 {
580 *thread_done = TRUE;
581 gtk_main_quit ();
582 return FALSE;
583 }
584
585 static gboolean
cs_auth_pam_verify_user(pam_handle_t * handle,int * status)586 cs_auth_pam_verify_user (pam_handle_t *handle,
587 int *status)
588 {
589 GThread *auth_thread;
590 GIOChannel *channel;
591 guint watch_id;
592 int auth_operation_fds[2];
593 int auth_status;
594 gboolean thread_done;
595
596 channel = NULL;
597 watch_id = 0;
598 auth_status = PAM_AUTH_ERR;
599
600 /* This pipe gives us a set of fds we can hook into
601 * the event loop to be notified when our helper thread
602 * is ready to be reaped.
603 */
604 if (pipe (auth_operation_fds) < 0) {
605 goto out;
606 }
607
608 if (fcntl (auth_operation_fds[0], F_SETFD, FD_CLOEXEC) < 0) {
609 close (auth_operation_fds[0]);
610 close (auth_operation_fds[1]);
611 goto out;
612 }
613
614 if (fcntl (auth_operation_fds[1], F_SETFD, FD_CLOEXEC) < 0) {
615 close (auth_operation_fds[0]);
616 close (auth_operation_fds[1]);
617 goto out;
618 }
619
620 channel = g_io_channel_unix_new (auth_operation_fds[0]);
621
622 /* we use a recursive main loop to process ui events
623 * while we wait on a thread to handle the blocking parts
624 * of pam authentication.
625 */
626 thread_done = FALSE;
627 watch_id = g_io_add_watch (channel, G_IO_ERR | G_IO_HUP,
628 (GIOFunc) cs_auth_loop_quit, &thread_done);
629
630 auth_thread = g_thread_create ((GThreadFunc) cs_auth_thread_func,
631 GINT_TO_POINTER (auth_operation_fds[1]),
632 TRUE, NULL);
633
634 if (auth_thread == NULL) {
635 goto out;
636 }
637
638 gtk_main ();
639
640 /* if the event loop was quit before the thread is done then we can't
641 * reap the thread without blocking on it finishing. The
642 * thread may not ever finish though if the pam module is blocking.
643 *
644 * The only time the event loop is going to stop when the thread isn't
645 * done, however, is if the dialog quits early (from, e.g., "cancel"),
646 * so we can just exit. An alternative option would be to switch to
647 * using pthreads directly and calling pthread_cancel.
648 */
649 if (!thread_done) {
650 raise (SIGTERM);
651 }
652
653 auth_status = GPOINTER_TO_INT (g_thread_join (auth_thread));
654
655 out:
656 if (watch_id != 0 && !thread_done) {
657 g_source_remove (watch_id);
658 watch_id = 0;
659 }
660
661 if (channel != NULL) {
662 g_io_channel_unref (channel);
663 }
664
665 if (status) {
666 *status = auth_status;
667 }
668
669 return auth_status == PAM_SUCCESS;
670 }
671
672 /**
673 * cs_auth_verify_user:
674 * @username: user name
675 * @display: display string
676 * @func: (scope async): the auth function callback
677 * @data: (closure func): data for func
678 * @error: Return location for error or %NULL.
679 *
680 * Starts a PAM thread for user authentication.
681 *
682 * Returns: Whether or not the user was authenticated successfully
683 */
684
685 gboolean
cs_auth_verify_user(const char * username,const char * display,CsAuthMessageFunc func,gpointer data,GError ** error)686 cs_auth_verify_user (const char *username,
687 const char *display,
688 CsAuthMessageFunc func,
689 gpointer data,
690 GError **error)
691 {
692 int status = -1;
693 struct pam_conv conv;
694 struct pam_closure c;
695 struct passwd *pwent;
696
697 pwent = getpwnam (username);
698 if (pwent == NULL) {
699 return FALSE;
700 }
701
702 c.username = username;
703 c.cb_func = func;
704 c.cb_data = data;
705
706 conv.conv = &pam_conversation;
707 conv.appdata_ptr = (void *) &c;
708
709 /* Initialize PAM. */
710 create_pam_handle (username, display, &conv, &status);
711 if (status != PAM_SUCCESS) {
712 goto done;
713 }
714
715 pam_set_item (pam_handle, PAM_USER_PROMPT, _("Username:"));
716
717 did_we_ask_for_password = FALSE;
718 if (! cs_auth_pam_verify_user (pam_handle, &status)) {
719 goto done;
720 }
721
722 done:
723 if (status != PAM_SUCCESS) {
724 set_pam_error (error, status);
725 }
726
727 close_pam_handle (status);
728
729 return (status == PAM_SUCCESS ? TRUE : FALSE);
730 }
731
732 gboolean
cs_auth_init(void)733 cs_auth_init (void)
734 {
735 return TRUE;
736 }
737
738 gboolean
cs_auth_priv_init(void)739 cs_auth_priv_init (void)
740 {
741 /* We have nothing to do at init-time.
742 However, we might as well do some error checking.
743 If "/etc/pam.d" exists and is a directory, but "/etc/pam.d/xlock"
744 does not exist, warn that PAM probably isn't going to work.
745
746 This is a priv-init instead of a non-priv init in case the directory
747 is unreadable or something (don't know if that actually happens.)
748 */
749 const char dir [] = "/etc/pam.d";
750 const char file [] = "/etc/pam.d/" PAM_SERVICE_NAME;
751 const char file2 [] = "/etc/pam.conf";
752 struct stat st;
753
754 if (g_stat (dir, &st) == 0 && st.st_mode & S_IFDIR) {
755 if (g_stat (file, &st) != 0) {
756 g_warning ("%s does not exist.\n"
757 "Authentication via PAM is unlikely to work.",
758 file);
759 }
760 } else if (g_stat (file2, &st) == 0) {
761 FILE *f = g_fopen (file2, "r");
762 if (f) {
763 gboolean ok = FALSE;
764 char buf[255];
765 while (fgets (buf, sizeof(buf), f)) {
766 if (strstr (buf, PAM_SERVICE_NAME)) {
767 ok = TRUE;
768 break;
769 }
770 }
771
772 fclose (f);
773 if (!ok) {
774 g_warning ("%s does not list the `%s' service.\n"
775 "Authentication via PAM is unlikely to work.",
776 file2, PAM_SERVICE_NAME);
777 }
778 }
779 /* else warn about file2 existing but being unreadable? */
780 } else {
781 g_warning ("Neither %s nor %s exist.\n"
782 "Authentication via PAM is unlikely to work.",
783 file2, file);
784 }
785
786 /* Return true anyway, just in case. */
787 return TRUE;
788 }
789