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