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