chiark / gitweb /
Add pam configuration to allow user sessions to work out of the box
[elogind.git] / src / login / pam-module.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd 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   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <sys/file.h>
25 #include <pwd.h>
26 #include <endian.h>
27 #include <sys/capability.h>
28
29 #include <security/pam_modules.h>
30 #include <security/_pam_macros.h>
31 #include <security/pam_modutil.h>
32 #include <security/pam_ext.h>
33 #include <security/pam_misc.h>
34
35 #include "util.h"
36 #include "audit.h"
37 #include "macro.h"
38 #include "strv.h"
39 #include "dbus-common.h"
40 #include "def.h"
41 #include "socket-util.h"
42 #include "fileio.h"
43
44 static int parse_argv(pam_handle_t *handle,
45                       int argc, const char **argv,
46                       const char **class,
47                       bool *debug) {
48
49         unsigned i;
50
51         assert(argc >= 0);
52         assert(argc == 0 || argv);
53
54         for (i = 0; i < (unsigned) argc; i++) {
55                 int k;
56
57                 if (startswith(argv[i], "class=")) {
58
59                         if (class)
60                                 *class = argv[i] + 6;
61
62                 } else if (startswith(argv[i], "debug=")) {
63                         k = parse_boolean(argv[i] + 6);
64
65                         if (k < 0) {
66                                 pam_syslog(handle, LOG_ERR, "Failed to parse debug= argument.");
67                                 return k;
68                         }
69
70                         if (debug)
71                                 *debug = k;
72
73                 } else {
74                         pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
75                         return 0;
76                 }
77         }
78
79         return 0;
80 }
81
82 static int get_user_data(
83                 pam_handle_t *handle,
84                 const char **ret_username,
85                 struct passwd **ret_pw) {
86
87         const char *username = NULL;
88         struct passwd *pw = NULL;
89         uid_t uid;
90         int r;
91
92         assert(handle);
93         assert(ret_username);
94         assert(ret_pw);
95
96         r = audit_loginuid_from_pid(0, &uid);
97         if (r >= 0)
98                 pw = pam_modutil_getpwuid(handle, uid);
99         else {
100                 r = pam_get_user(handle, &username, NULL);
101                 if (r != PAM_SUCCESS) {
102                         pam_syslog(handle, LOG_ERR, "Failed to get user name.");
103                         return r;
104                 }
105
106                 if (isempty(username)) {
107                         pam_syslog(handle, LOG_ERR, "User name not valid.");
108                         return PAM_AUTH_ERR;
109                 }
110
111                 pw = pam_modutil_getpwnam(handle, username);
112         }
113
114         if (!pw) {
115                 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
116                 return PAM_USER_UNKNOWN;
117         }
118
119         *ret_pw = pw;
120         *ret_username = username ? username : pw->pw_name;
121
122         return PAM_SUCCESS;
123 }
124
125 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
126         _cleanup_free_ char *p = NULL;
127         int r;
128         _cleanup_close_ int fd = -1;
129         union sockaddr_union sa = {
130                 .un.sun_family = AF_UNIX,
131         };
132         struct ucred ucred;
133         socklen_t l;
134         _cleanup_free_ char *tty = NULL;
135         int v;
136
137         assert(display);
138         assert(vtnr);
139
140         /* We deduce the X11 socket from the display name, then use
141          * SO_PEERCRED to determine the X11 server process, ask for
142          * the controlling tty of that and if it's a VC then we know
143          * the seat and the virtual terminal. Sounds ugly, is only
144          * semi-ugly. */
145
146         r = socket_from_display(display, &p);
147         if (r < 0)
148                 return r;
149         strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
150
151         fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
152         if (fd < 0)
153                 return -errno;
154
155         if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
156                 return -errno;
157
158         l = sizeof(ucred);
159         r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l);
160         if (r < 0)
161                 return -errno;
162
163         r = get_ctty(ucred.pid, NULL, &tty);
164         if (r < 0)
165                 return r;
166
167         v = vtnr_from_tty(tty);
168         if (v < 0)
169                 return v;
170         else if (v == 0)
171                 return -ENOENT;
172
173         if (seat)
174                 *seat = "seat0";
175         *vtnr = (uint32_t) v;
176
177         return 0;
178 }
179
180 _public_ PAM_EXTERN int pam_sm_open_session(
181                 pam_handle_t *handle,
182                 int flags,
183                 int argc, const char **argv) {
184
185         struct passwd *pw;
186         bool debug = false;
187         const char *username, *id, *object_path, *runtime_path, *service = NULL, *tty = NULL, *display = NULL, *remote_user = NULL, *remote_host = NULL, *seat = NULL, *type = NULL, *class = NULL, *class_pam = NULL, *cvtnr = NULL;
188         DBusError error;
189         uint32_t uid, pid;
190         DBusMessageIter iter;
191         int session_fd = -1;
192         DBusConnection *bus = NULL;
193         DBusMessage *m = NULL, *reply = NULL;
194         dbus_bool_t remote, existing;
195         int r;
196         uint32_t vtnr = 0;
197
198         assert(handle);
199
200         dbus_error_init(&error);
201
202         /* pam_syslog(handle, LOG_INFO, "pam-systemd initializing"); */
203
204         /* Make this a NOP on non-logind systems */
205         if (!logind_running())
206                 return PAM_SUCCESS;
207
208         if (parse_argv(handle,
209                        argc, argv,
210                        &class_pam,
211                        &debug) < 0) {
212                 r = PAM_SESSION_ERR;
213                 goto finish;
214         }
215
216         r = get_user_data(handle, &username, &pw);
217         if (r != PAM_SUCCESS)
218                 goto finish;
219
220         /* Make sure we don't enter a loop by talking to
221          * systemd-logind when it is actually waiting for the
222          * background to finish start-up. If the service is
223          * "systemd-user" we simply set XDG_RUNTIME_DIR and
224          * leave. */
225
226         pam_get_item(handle, PAM_SERVICE, (const void**) &service);
227         if (streq_ptr(service, "systemd-user")) {
228                 char *p, *rt = NULL;
229
230                 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0) {
231                         r = PAM_BUF_ERR;
232                         goto finish;
233                 }
234
235                 r = parse_env_file(p, NEWLINE,
236                                    "RUNTIME", &rt,
237                                    NULL);
238                 free(p);
239
240                 if (r < 0 && r != -ENOENT) {
241                         r = PAM_SESSION_ERR;
242                         free(rt);
243                         goto finish;
244                 }
245
246                 if (rt)  {
247                         r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
248                         free(rt);
249
250                         if (r != PAM_SUCCESS) {
251                                 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
252                                 goto finish;
253                         }
254                 }
255
256                 r = PAM_SUCCESS;
257                 goto finish;
258         }
259
260         dbus_connection_set_change_sigpipe(FALSE);
261
262         bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
263         if (!bus) {
264                 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
265                 r = PAM_SESSION_ERR;
266                 goto finish;
267         }
268
269         m = dbus_message_new_method_call(
270                         "org.freedesktop.login1",
271                         "/org/freedesktop/login1",
272                         "org.freedesktop.login1.Manager",
273                         "CreateSession");
274         if (!m) {
275                 pam_syslog(handle, LOG_ERR, "Could not allocate create session message.");
276                 r = PAM_BUF_ERR;
277                 goto finish;
278         }
279
280         uid = pw->pw_uid;
281         pid = getpid();
282
283         pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
284         pam_get_item(handle, PAM_TTY, (const void**) &tty);
285         pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
286         pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
287
288         seat = pam_getenv(handle, "XDG_SEAT");
289         if (isempty(seat))
290                 seat = getenv("XDG_SEAT");
291
292         cvtnr = pam_getenv(handle, "XDG_VTNR");
293         if (isempty(cvtnr))
294                 cvtnr = getenv("XDG_VTNR");
295
296         service = strempty(service);
297         tty = strempty(tty);
298         display = strempty(display);
299         remote_user = strempty(remote_user);
300         remote_host = strempty(remote_host);
301         seat = strempty(seat);
302
303         if (strchr(tty, ':')) {
304                 /* A tty with a colon is usually an X11 display,
305                  * placed there to show up in utmp. We rearrange
306                  * things and don't pretend that an X display was a
307                  * tty. */
308
309                 if (isempty(display))
310                         display = tty;
311                 tty = "";
312         } else if (streq(tty, "cron")) {
313                 /* cron has been setting PAM_TTY to "cron" for a very
314                  * long time and it probably shouldn't stop doing that
315                  * for compatibility reasons. */
316                 tty = "";
317                 type = "unspecified";
318         } else if (streq(tty, "ssh")) {
319                 /* ssh has been setting PAM_TTY to "ssh" for a very
320                  * long time and probably shouldn't stop doing that
321                  * for compatibility reasons. */
322                 tty = "";
323                 type ="tty";
324         }
325
326         /* If this fails vtnr will be 0, that's intended */
327         if (!isempty(cvtnr))
328                 safe_atou32(cvtnr, &vtnr);
329
330         if (!isempty(display) && vtnr <= 0) {
331                 if (isempty(seat))
332                         get_seat_from_display(display, &seat, &vtnr);
333                 else if (streq(seat, "seat0"))
334                         get_seat_from_display(display, NULL, &vtnr);
335         }
336
337         if (!type)
338                 type = !isempty(display) ? "x11" :
339                         !isempty(tty) ? "tty" : "unspecified";
340
341         class = pam_getenv(handle, "XDG_SESSION_CLASS");
342         if (isempty(class))
343                 class = getenv("XDG_SESSION_CLASS");
344         if (isempty(class))
345                 class = class_pam;
346         if (isempty(class))
347                 class = streq(type, "unspecified") ? "background" : "user";
348
349         remote = !isempty(remote_host) &&
350                 !streq(remote_host, "localhost") &&
351                 !streq(remote_host, "localhost.localdomain");
352
353         if (!dbus_message_append_args(m,
354                                       DBUS_TYPE_UINT32, &uid,
355                                       DBUS_TYPE_UINT32, &pid,
356                                       DBUS_TYPE_STRING, &service,
357                                       DBUS_TYPE_STRING, &type,
358                                       DBUS_TYPE_STRING, &class,
359                                       DBUS_TYPE_STRING, &seat,
360                                       DBUS_TYPE_UINT32, &vtnr,
361                                       DBUS_TYPE_STRING, &tty,
362                                       DBUS_TYPE_STRING, &display,
363                                       DBUS_TYPE_BOOLEAN, &remote,
364                                       DBUS_TYPE_STRING, &remote_user,
365                                       DBUS_TYPE_STRING, &remote_host,
366                                       DBUS_TYPE_INVALID)) {
367                 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
368                 r = PAM_BUF_ERR;
369                 goto finish;
370         }
371
372         dbus_message_iter_init_append(m, &iter);
373
374         if (debug)
375                 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
376                            "uid=%u pid=%u service=%s type=%s class=%s seat=%s vtnr=%u tty=%s display=%s remote=%s remote_user=%s remote_host=%s",
377                            uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
378
379         reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
380         if (!reply) {
381                 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error));
382                 r = PAM_SESSION_ERR;
383                 goto finish;
384         }
385
386         if (!dbus_message_get_args(reply, &error,
387                                    DBUS_TYPE_STRING, &id,
388                                    DBUS_TYPE_OBJECT_PATH, &object_path,
389                                    DBUS_TYPE_STRING, &runtime_path,
390                                    DBUS_TYPE_UNIX_FD, &session_fd,
391                                    DBUS_TYPE_STRING, &seat,
392                                    DBUS_TYPE_UINT32, &vtnr,
393                                    DBUS_TYPE_BOOLEAN, &existing,
394                                    DBUS_TYPE_INVALID)) {
395                 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
396                 r = PAM_SESSION_ERR;
397                 goto finish;
398         }
399
400         if (debug)
401                 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
402                            "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
403                            id, object_path, runtime_path, session_fd, seat, vtnr);
404
405         r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
406         if (r != PAM_SUCCESS) {
407                 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
408                 goto finish;
409         }
410
411         r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
412         if (r != PAM_SUCCESS) {
413                 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
414                 goto finish;
415         }
416
417         if (!isempty(seat)) {
418                 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
419                 if (r != PAM_SUCCESS) {
420                         pam_syslog(handle, LOG_ERR, "Failed to set seat.");
421                         goto finish;
422                 }
423         }
424
425         if (vtnr > 0) {
426                 char buf[11];
427                 snprintf(buf, sizeof(buf), "%u", vtnr);
428                 char_array_0(buf);
429
430                 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
431                 if (r != PAM_SUCCESS) {
432                         pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
433                         goto finish;
434                 }
435         }
436
437         r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
438         if (r != PAM_SUCCESS) {
439                 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
440                 return r;
441         }
442
443         if (session_fd >= 0) {
444                 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
445                 if (r != PAM_SUCCESS) {
446                         pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
447                         return r;
448                 }
449         }
450
451         session_fd = -1;
452
453         r = PAM_SUCCESS;
454
455 finish:
456         dbus_error_free(&error);
457
458         if (bus) {
459                 dbus_connection_close(bus);
460                 dbus_connection_unref(bus);
461         }
462
463         if (m)
464                 dbus_message_unref(m);
465
466         if (reply)
467                 dbus_message_unref(reply);
468
469         if (session_fd >= 0)
470                 close_nointr_nofail(session_fd);
471
472         return r;
473 }
474
475 _public_ PAM_EXTERN int pam_sm_close_session(
476                 pam_handle_t *handle,
477                 int flags,
478                 int argc, const char **argv) {
479
480         const void *p = NULL, *existing = NULL;
481         const char *id;
482         DBusConnection *bus = NULL;
483         DBusMessage *m = NULL, *reply = NULL;
484         DBusError error;
485         int r;
486
487         assert(handle);
488
489         dbus_error_init(&error);
490
491         /* Only release session if it wasn't pre-existing when we
492          * tried to create it */
493         pam_get_data(handle, "systemd.existing", &existing);
494
495         id = pam_getenv(handle, "XDG_SESSION_ID");
496         if (id && !existing) {
497
498                 /* Before we go and close the FIFO we need to tell
499                  * logind that this is a clean session shutdown, so
500                  * that it doesn't just go and slaughter us
501                  * immediately after closing the fd */
502
503                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
504                 if (!bus) {
505                         pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
506                         r = PAM_SESSION_ERR;
507                         goto finish;
508                 }
509
510                 m = dbus_message_new_method_call(
511                                 "org.freedesktop.login1",
512                                 "/org/freedesktop/login1",
513                                 "org.freedesktop.login1.Manager",
514                                 "ReleaseSession");
515                 if (!m) {
516                         pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
517                         r = PAM_BUF_ERR;
518                         goto finish;
519                 }
520
521                 if (!dbus_message_append_args(m,
522                                               DBUS_TYPE_STRING, &id,
523                                               DBUS_TYPE_INVALID)) {
524                         pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
525                         r = PAM_BUF_ERR;
526                         goto finish;
527                 }
528
529                 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
530                 if (!reply) {
531                         pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
532                         r = PAM_SESSION_ERR;
533                         goto finish;
534                 }
535         }
536
537         r = PAM_SUCCESS;
538
539 finish:
540         pam_get_data(handle, "systemd.session-fd", &p);
541         if (p)
542                 close_nointr(PTR_TO_INT(p) - 1);
543
544         dbus_error_free(&error);
545
546         if (bus) {
547                 dbus_connection_close(bus);
548                 dbus_connection_unref(bus);
549         }
550
551         if (m)
552                 dbus_message_unref(m);
553
554         if (reply)
555                 dbus_message_unref(reply);
556
557         return r;
558 }