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