1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2  *
3  * pam-ck-connector.c : PAM module for registering with CK
4  *
5  * Copyright (c) 2007 David Zeuthen <davidz@redhat.com>
6  *
7  * Permission is hereby granted, free of charge, to any person
8  * obtaining a copy of this software and associated documentation
9  * files (the "Software"), to deal in the Software without
10  * restriction, including without limitation the rights to use,
11  * copy, modify, merge, publish, distribute, sublicense, and/or sell
12  * copies of the Software, and to permit persons to whom the
13  * Software is furnished to do so, subject to the following
14  * conditions:
15  *
16  * The above copyright notice and this permission notice shall be
17  * included in all copies or substantial portions of the Software.
18  *
19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
21  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26  * OTHER DEALINGS IN THE SOFTWARE.
27  */
28 
29 #include "config.h"
30 
31 #include <ctype.h>
32 #include <pwd.h>
33 #include <stdarg.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <syslog.h>
38 #include <sys/stat.h>
39 #include <sys/types.h>
40 #include <unistd.h>
41 #include <dirent.h>
42 #include <limits.h>
43 #include <errno.h>
44 
45 #ifdef HAVE_PATHS_H
46 #include <paths.h>
47 #endif /* HAVE_PATHS_H */
48 
49 #ifndef _PATH_DEV
50 #define _PATH_DEV "/dev/"
51 #endif
52 
53 #define PAM_SM_SESSION
54 
55 #include <security/pam_appl.h>
56 #include <security/pam_modules.h>
57 #ifdef HAVE_SECURITY_PAM_MODUTIL_H
58 #include <security/pam_modutil.h>
59 #endif
60 #ifdef HAVE_SECURITY_PAM_EXT_H
61 #include <security/pam_ext.h>
62 #endif
63 
64 #include "ck-connector.h"
65 
66 static int opt_debug = FALSE;
67 static int opt_nox11 = FALSE;
68 
69 #ifndef HAVE_PAM_SYSLOG
70 
71 #ifndef LOG_AUTHPRIV
72 #define LOG_AUTHPRIV LOG_AUTH
73 #endif
74 
75 #ifndef PAM_EXTERN
76 #ifdef PAM_STATIC
77 #define PAM_EXTERN static
78 #else
79 #define PAM_EXTERN extern
80 #endif
81 #endif
82 
83 static void
ck_pam_vsyslog(const pam_handle_t * pamh,int priority,const char * fmt,va_list args)84 ck_pam_vsyslog (const pam_handle_t *pamh,
85                 int                 priority,
86                 const char         *fmt,
87                 va_list             args)
88 {
89         char        msgbuf1 [1024];
90         char        msgbuf2 [1024];
91         int         save_errno;
92         const char *service;
93         const char *mod_name;
94         const char *choice;
95         int         res;
96 
97         save_errno = errno;
98         mod_name = "pam_ck_connector";
99         choice = "session";
100 
101         if (pamh != NULL) {
102                 res = pam_get_item (pamh, PAM_SERVICE, (void *) &service);
103                 if (service == NULL || *service == '\0' || res != PAM_SUCCESS) {
104                         service = "<unknown>";
105                 }
106         } else {
107                 service = "<unknown>";
108         }
109 
110         res = snprintf (msgbuf1,
111                         sizeof (msgbuf1),
112                         "%s(%s:%s):",
113                         mod_name,
114                         service,
115                         choice);
116         if (res < 0) {
117                 return;
118         }
119 
120         errno = save_errno;
121         res = vsnprintf (msgbuf2, sizeof (msgbuf2), fmt, args);
122         if (res < 0) {
123                 return;
124         }
125 
126         errno = save_errno;
127         syslog (LOG_AUTHPRIV|priority, "%s %s", msgbuf1, msgbuf2);
128 }
129 
130 static void
ck_pam_syslog(const pam_handle_t * pamh,int priority,const char * fmt,...)131 ck_pam_syslog (const pam_handle_t *pamh,
132                int                 priority,
133                const char         *fmt,
134                ...)
135 {
136         va_list args;
137 
138         va_start (args, fmt);
139         ck_pam_vsyslog (pamh, priority, fmt, args);
140         va_end (args);
141 }
142 #else
143 #define ck_pam_syslog(pamh, priority, ...) pam_syslog(pamh, priority, __VA_ARGS__)
144 #endif
145 
146 static void
_parse_pam_args(const pam_handle_t * pamh,int flags,int argc,const char ** argv)147 _parse_pam_args (const pam_handle_t *pamh,
148                  int                 flags,
149                  int                 argc,
150                  const char        **argv)
151 {
152         int i;
153 
154         opt_debug = FALSE;
155         for (i = 0; i < argc && argv[i] != NULL; i++) {
156                 if (strcmp (argv[i] , "debug") == 0) {
157                         opt_debug = TRUE;
158                 } else if (strcmp (argv[i] , "nox11") == 0) {
159                         opt_nox11 = TRUE;
160                 } else {
161                         ck_pam_syslog (pamh, LOG_ERR, "unknown option: %s", argv[i]);
162                 }
163         }
164 }
165 
166 
167 PAM_EXTERN int
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,const char ** argv)168 pam_sm_authenticate (pam_handle_t *pamh,
169                      int           flags,
170                      int           argc,
171                      const char  **argv)
172 {
173         return PAM_IGNORE;
174 }
175 
176 PAM_EXTERN int
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,const char ** argv)177 pam_sm_setcred (pam_handle_t *pamh,
178                 int           flags,
179                 int           argc,
180                 const char  **argv)
181 {
182         return PAM_IGNORE;
183 }
184 
185 static uid_t
_util_name_to_uid(const char * username,gid_t * default_gid)186 _util_name_to_uid (const char *username,
187                    gid_t      *default_gid)
188 {
189         int           rc;
190         uid_t         res;
191         char         *buf = NULL;
192         long          bufsize;
193         struct passwd pwd;
194         struct passwd *pwdp;
195 
196         res = (uid_t) -1;
197 
198         bufsize = sysconf (_SC_GETPW_R_SIZE_MAX);
199         if (bufsize == -1) {
200 		// assume 1024, not all systems (e.g musl) have _SC_GETPW_R_SIZE_MAX
201 		bufsize = 1024;
202         }
203         buf = calloc (sizeof (char), bufsize);
204         rc = getpwnam_r (username, &pwd, buf, bufsize, &pwdp);
205         if (rc != 0 || pwdp == NULL) {
206                 goto out;
207         }
208 
209         res = pwdp->pw_uid;
210         if (default_gid != NULL) {
211                 *default_gid = pwdp->pw_gid;
212         }
213 
214 out:
215         free (buf);
216         return res;
217 }
218 
219 /* our singleton */
220 static CkConnector *ckc = NULL;
221 
222 PAM_EXTERN int
pam_sm_close_session(pam_handle_t * pamh,int flags,int argc,const char ** argv)223 pam_sm_close_session (pam_handle_t *pamh,
224                       int           flags,
225                       int           argc,
226                       const char  **argv)
227 {
228         if (ckc != NULL) {
229                 ck_connector_unref (ckc);
230                 ckc = NULL;
231         }
232         return PAM_SUCCESS;
233 }
234 
235 PAM_EXTERN int
pam_sm_open_session(pam_handle_t * pamh,int flags,int argc,const char ** argv)236 pam_sm_open_session (pam_handle_t *pamh,
237                      int           flags,
238                      int           argc,
239                      const char  **argv)
240 {
241         int         ret;
242         int         res;
243         const char *user;
244         const char *display_device;
245         const char *x11_display;
246         const char *x11_display_device;
247         const char *remote_host_name;
248         const char *runtime_dir;
249         const char *s;
250         uid_t       uid;
251         char        buf[256];
252         char       *ttybuf;
253         char       *xdd;
254         DBusError   error;
255         dbus_bool_t is_local;
256 
257         ret = PAM_IGNORE;
258         ttybuf = NULL;
259         xdd = NULL;
260         is_local = TRUE;
261 
262         _parse_pam_args (pamh, flags, argc, argv);
263 
264         /* Register with ConsoleKit as part of the session management */
265         if (ckc != NULL) {
266                 ck_pam_syslog (pamh, LOG_ERR, "process already registered with ConsoleKit");
267                 goto out;
268         }
269 
270         /* set a global flag so that D-Bus does not change the SIGPIPE handler.
271            See https://bugzilla.redhat.com/show_bug.cgi?id=430431
272         */
273         dbus_connection_set_change_sigpipe (FALSE);
274 
275         ckc = ck_connector_new ();
276         if (ckc == NULL) {
277                 ck_pam_syslog (pamh, LOG_ERR, "oom creating ConsoleKit connector object");
278                 goto out;
279         }
280 
281         user = NULL;
282         res = pam_get_user (pamh, &user, NULL);
283         if (res != PAM_SUCCESS || user == NULL || user[0] == '\0') {
284                 ck_pam_syslog (pamh, LOG_ERR, "cannot determine username");
285                 goto out;
286         }
287 
288         display_device = NULL;
289         res = pam_get_item (pamh, PAM_TTY, (const void **) &display_device);
290         if (res != PAM_SUCCESS || display_device == NULL || display_device[0] == '\0') {
291                 ck_pam_syslog (pamh, LOG_ERR, "cannot determine display-device");
292                 goto out;
293         }
294 
295         x11_display = NULL;
296         /* interpret any tty with a colon as a DISPLAY */
297         if (strchr (display_device, ':') != NULL) {
298                 if (opt_nox11) {
299                         ck_pam_syslog (pamh, LOG_WARNING, "nox11 mode, ignoring PAM_TTY %s", display_device);
300                         goto out;
301                 }
302                 x11_display = display_device;
303                 display_device = "";
304         } else if (strncmp (_PATH_DEV, display_device, 5) != 0) {
305                 int len = strlen (_PATH_DEV) + strlen (display_device) + 1;
306                 ttybuf = malloc (len);
307                 if (ttybuf == NULL) {
308                         ck_pam_syslog (pamh, LOG_ERR, "oom allocating ttybuf");
309                         goto out;
310                 }
311                 snprintf (ttybuf, len, _PATH_DEV "%s", display_device);
312                 display_device = ttybuf;
313         }
314 
315         remote_host_name = NULL;
316         s = NULL;
317         res = pam_get_item (pamh, PAM_RHOST, (const void **) &s);
318         if (res == PAM_SUCCESS && s != NULL && s[0] != '\0') {
319                 remote_host_name = s;
320                 if (opt_debug) {
321                         ck_pam_syslog (pamh, LOG_INFO, "using '%s' as remote-host-name", remote_host_name);
322                 }
323                 is_local = FALSE;
324         }
325 
326         if ((s = pam_getenv (pamh, "CKCON_TTY")) != NULL) {
327                 display_device = s;
328                 if (opt_debug) {
329                         ck_pam_syslog (pamh, LOG_INFO, "using '%s' as display-device (from CKCON_TTY)", display_device);
330                 }
331         }
332 
333         if ((s = pam_getenv (pamh, "CKCON_X11_DISPLAY")) != NULL) {
334                 x11_display = s;
335                 if (opt_debug) {
336                         ck_pam_syslog (pamh, LOG_INFO, "using '%s' as X11 display (from CKCON_X11_DISPLAY)", x11_display);
337                 }
338         }
339 
340         x11_display_device = NULL;
341         if ((s = pam_getenv (pamh, "CKCON_X11_DISPLAY_DEVICE")) != NULL) {
342                 x11_display_device = s;
343                 if (opt_debug) {
344                         ck_pam_syslog (pamh, LOG_INFO, "using '%s' as X11 display device (from CKCON_X11_DISPLAY_DEVICE)", x11_display_device);
345                 }
346         } else if ((s = pam_getenv (pamh, "XDG_VTNR")) != NULL) {
347                 int len = strlen (s) + 10; /* room for "/dev/ttyXX\0" */
348                 xdd = malloc (len);
349                 snprintf (xdd, len, "/dev/tty%s", s);
350                 x11_display_device = xdd;
351                 if (opt_debug) {
352                         ck_pam_syslog (pamh, LOG_INFO, "using '%s' as X11 display device (from XDG_VTNR)", x11_display_device);
353                 }
354         }
355 
356         uid = _util_name_to_uid (user, NULL);
357         if (uid == (uid_t) -1) {
358                 ck_pam_syslog (pamh, LOG_ERR, "cannot determine uid for user '%s'", user);
359                 goto out;
360         } else {
361                 if (opt_debug) {
362                         ck_pam_syslog (pamh, LOG_INFO, "using %d as uid", uid);
363                 }
364         }
365 
366         /* make sure no values are NULL */
367         if (x11_display == NULL) {
368                 x11_display = "";
369         }
370         if (x11_display_device == NULL) {
371                 x11_display_device = "";
372         }
373         if (remote_host_name == NULL) {
374                 remote_host_name = "";
375         }
376 
377         dbus_error_init (&error);
378         res = ck_connector_open_session_with_parameters (ckc,
379                                                          &error,
380                                                          "unix-user", &uid,
381                                                          "display-device", &display_device,
382                                                          "x11-display", &x11_display,
383                                                          "x11-display-device", &x11_display_device,
384                                                          "remote-host-name", &remote_host_name,
385                                                          "is-local", &is_local,
386                                                          NULL);
387         if (opt_debug) {
388                 ck_pam_syslog (pamh, LOG_INFO, "open session result: %d", res);
389         }
390 
391         if (! res) {
392                 /* this might not be a bug for servers that don't have
393                  * the message bus or ConsoleKit daemon running - so
394                  * only log a message in debugging mode.
395                  */
396                 if (dbus_error_is_set (&error)) {
397                         if (opt_debug) {
398                                 ck_pam_syslog (pamh, LOG_DEBUG, "%s", error.message);
399                         }
400                         dbus_error_free (&error);
401                 } else {
402                         if (opt_debug) {
403                                 ck_pam_syslog (pamh, LOG_DEBUG, "insufficient privileges or D-Bus / ConsoleKit not available");
404                         }
405                 }
406 
407                 goto out;
408         }
409 
410         /* now set the cookie */
411         buf[sizeof (buf) - 1] = '\0';
412         snprintf (buf, sizeof (buf) - 1, "XDG_SESSION_COOKIE=%s", ck_connector_get_cookie (ckc));
413         if (pam_putenv (pamh, buf) != PAM_SUCCESS) {
414                 ck_pam_syslog (pamh, LOG_ERR, "unable to set XDG_SESSION_COOKIE in environment");
415                 /* tear down session the hard way */
416                 ck_connector_unref (ckc);
417                 ckc = NULL;
418 
419                 goto out;
420         }
421 
422         /* and set the runtime dir */
423         buf[sizeof (buf) - 1] = '\0';
424         runtime_dir = ck_connector_get_runtime_dir (ckc, &error);
425         if (runtime_dir != NULL) {
426                 snprintf (buf, sizeof (buf) - 1, "XDG_RUNTIME_DIR=%s", runtime_dir);
427                 if (pam_putenv (pamh, buf) != PAM_SUCCESS) {
428                         ck_pam_syslog (pamh, LOG_ERR, "unable to set XDG_RUNTIME_DIR in environment");
429                         /* tear down session the hard way */
430                         ck_connector_unref (ckc);
431                         ckc = NULL;
432 
433                         goto out;
434                 }
435         }
436 
437         if (opt_debug) {
438                 ck_pam_syslog (pamh, LOG_DEBUG, "registered uid=%d on tty='%s' with ConsoleKit", uid, display_device);
439         }
440 
441         /* note that we're leaking our CkConnector instance ckc - this
442          * is *by design* such that when the login manager (that uses
443          * us) exits / crashes / etc. ConsoleKit will notice, via D-Bus
444          * connection tracking, that the login session ended.
445          */
446 
447         ret = PAM_SUCCESS;
448 
449 out:
450         free (ttybuf);
451         free (xdd);
452 
453         return ret;
454 }
455 
456 #ifdef PAM_STATIC
457 
458 struct pam_module _pam_ckconnector_modstruct = {
459         "pam_ck_connector",
460         pam_sm_authenticate,
461         pam_sm_setcred,
462         NULL,
463         pam_sm_open_session,
464         pam_sm_close_session,
465         NULL,
466 };
467 
468 #endif
469 
470 /* end of module definition */
471