chiark / gitweb /
logind: port logind to libsystemd-bus
[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, sub;
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         if (debug)
203                 pam_syslog(handle, LOG_INFO, "pam-systemd initializing");
204
205         /* Make this a NOP on non-logind systems */
206         if (!logind_running())
207                 return PAM_SUCCESS;
208
209         if (parse_argv(handle,
210                        argc, argv,
211                        &class_pam,
212                        &debug) < 0) {
213                 r = PAM_SESSION_ERR;
214                 goto finish;
215         }
216
217         r = get_user_data(handle, &username, &pw);
218         if (r != PAM_SUCCESS)
219                 goto finish;
220
221         /* Make sure we don't enter a loop by talking to
222          * systemd-logind when it is actually waiting for the
223          * background to finish start-up. If the service is
224          * "systemd-user" we simply set XDG_RUNTIME_DIR and
225          * leave. */
226
227         pam_get_item(handle, PAM_SERVICE, (const void**) &service);
228         if (streq_ptr(service, "systemd-user")) {
229                 char *p, *rt = NULL;
230
231                 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0) {
232                         r = PAM_BUF_ERR;
233                         goto finish;
234                 }
235
236                 r = parse_env_file(p, NEWLINE,
237                                    "RUNTIME", &rt,
238                                    NULL);
239                 free(p);
240
241                 if (r < 0 && r != -ENOENT) {
242                         r = PAM_SESSION_ERR;
243                         free(rt);
244                         goto finish;
245                 }
246
247                 if (rt)  {
248                         r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
249                         free(rt);
250
251                         if (r != PAM_SUCCESS) {
252                                 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
253                                 goto finish;
254                         }
255                 }
256
257                 r = PAM_SUCCESS;
258                 goto finish;
259         }
260
261         dbus_connection_set_change_sigpipe(FALSE);
262
263         bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
264         if (!bus) {
265                 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
266                 r = PAM_SESSION_ERR;
267                 goto finish;
268         }
269
270         m = dbus_message_new_method_call(
271                         "org.freedesktop.login1",
272                         "/org/freedesktop/login1",
273                         "org.freedesktop.login1.Manager",
274                         "CreateSession");
275         if (!m) {
276                 pam_syslog(handle, LOG_ERR, "Could not allocate create session message.");
277                 r = PAM_BUF_ERR;
278                 goto finish;
279         }
280
281         uid = pw->pw_uid;
282         pid = getpid();
283
284         pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
285         pam_get_item(handle, PAM_TTY, (const void**) &tty);
286         pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
287         pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
288
289         seat = pam_getenv(handle, "XDG_SEAT");
290         if (isempty(seat))
291                 seat = getenv("XDG_SEAT");
292
293         cvtnr = pam_getenv(handle, "XDG_VTNR");
294         if (isempty(cvtnr))
295                 cvtnr = getenv("XDG_VTNR");
296
297         service = strempty(service);
298         tty = strempty(tty);
299         display = strempty(display);
300         remote_user = strempty(remote_user);
301         remote_host = strempty(remote_host);
302         seat = strempty(seat);
303
304         if (strchr(tty, ':')) {
305                 /* A tty with a colon is usually an X11 display,
306                  * placed there to show up in utmp. We rearrange
307                  * things and don't pretend that an X display was a
308                  * tty. */
309
310                 if (isempty(display))
311                         display = tty;
312                 tty = "";
313         } else if (streq(tty, "cron")) {
314                 /* cron has been setting PAM_TTY to "cron" for a very
315                  * long time and it probably shouldn't stop doing that
316                  * for compatibility reasons. */
317                 tty = "";
318                 type = "unspecified";
319         } else if (streq(tty, "ssh")) {
320                 /* ssh has been setting PAM_TTY to "ssh" for a very
321                  * long time and probably shouldn't stop doing that
322                  * for compatibility reasons. */
323                 tty = "";
324                 type ="tty";
325         }
326
327         /* If this fails vtnr will be 0, that's intended */
328         if (!isempty(cvtnr))
329                 safe_atou32(cvtnr, &vtnr);
330
331         if (!isempty(display) && vtnr <= 0) {
332                 if (isempty(seat))
333                         get_seat_from_display(display, &seat, &vtnr);
334                 else if (streq(seat, "seat0"))
335                         get_seat_from_display(display, NULL, &vtnr);
336         }
337
338         if (!type)
339                 type = !isempty(display) ? "x11" :
340                         !isempty(tty) ? "tty" : "unspecified";
341
342         class = pam_getenv(handle, "XDG_SESSION_CLASS");
343         if (isempty(class))
344                 class = getenv("XDG_SESSION_CLASS");
345         if (isempty(class))
346                 class = class_pam;
347         if (isempty(class))
348                 class = streq(type, "unspecified") ? "background" : "user";
349
350         remote = !isempty(remote_host) &&
351                 !streq(remote_host, "localhost") &&
352                 !streq(remote_host, "localhost.localdomain");
353
354         if (!dbus_message_append_args(m,
355                                       DBUS_TYPE_UINT32, &uid,
356                                       DBUS_TYPE_UINT32, &pid,
357                                       DBUS_TYPE_STRING, &service,
358                                       DBUS_TYPE_STRING, &type,
359                                       DBUS_TYPE_STRING, &class,
360                                       DBUS_TYPE_STRING, &seat,
361                                       DBUS_TYPE_UINT32, &vtnr,
362                                       DBUS_TYPE_STRING, &tty,
363                                       DBUS_TYPE_STRING, &display,
364                                       DBUS_TYPE_BOOLEAN, &remote,
365                                       DBUS_TYPE_STRING, &remote_user,
366                                       DBUS_TYPE_STRING, &remote_host,
367                                       DBUS_TYPE_INVALID)) {
368                 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
369                 r = PAM_BUF_ERR;
370                 goto finish;
371         }
372
373         dbus_message_iter_init_append(m, &iter);
374
375         if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(sv)", &sub) ||
376             !dbus_message_iter_close_container(&iter, &sub)) {
377                 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
378                 r = PAM_BUF_ERR;
379                 goto finish;
380         }
381
382         if (debug)
383                 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
384                            "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",
385                            uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
386
387         reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
388         if (!reply) {
389                 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error));
390                 r = PAM_SESSION_ERR;
391                 goto finish;
392         }
393
394         if (!dbus_message_get_args(reply, &error,
395                                    DBUS_TYPE_STRING, &id,
396                                    DBUS_TYPE_OBJECT_PATH, &object_path,
397                                    DBUS_TYPE_STRING, &runtime_path,
398                                    DBUS_TYPE_UNIX_FD, &session_fd,
399                                    DBUS_TYPE_STRING, &seat,
400                                    DBUS_TYPE_UINT32, &vtnr,
401                                    DBUS_TYPE_BOOLEAN, &existing,
402                                    DBUS_TYPE_INVALID)) {
403                 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
404                 r = PAM_SESSION_ERR;
405                 goto finish;
406         }
407
408         if (debug)
409                 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
410                            "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
411                            id, object_path, runtime_path, session_fd, seat, vtnr);
412
413         r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
414         if (r != PAM_SUCCESS) {
415                 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
416                 goto finish;
417         }
418
419         r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
420         if (r != PAM_SUCCESS) {
421                 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
422                 goto finish;
423         }
424
425         if (!isempty(seat)) {
426                 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
427                 if (r != PAM_SUCCESS) {
428                         pam_syslog(handle, LOG_ERR, "Failed to set seat.");
429                         goto finish;
430                 }
431         }
432
433         if (vtnr > 0) {
434                 char buf[11];
435                 snprintf(buf, sizeof(buf), "%u", vtnr);
436                 char_array_0(buf);
437
438                 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
439                 if (r != PAM_SUCCESS) {
440                         pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
441                         goto finish;
442                 }
443         }
444
445         r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
446         if (r != PAM_SUCCESS) {
447                 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
448                 return r;
449         }
450
451         if (session_fd >= 0) {
452                 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
453                 if (r != PAM_SUCCESS) {
454                         pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
455                         return r;
456                 }
457         }
458
459         session_fd = -1;
460
461         r = PAM_SUCCESS;
462
463 finish:
464         dbus_error_free(&error);
465
466         if (bus) {
467                 dbus_connection_close(bus);
468                 dbus_connection_unref(bus);
469         }
470
471         if (m)
472                 dbus_message_unref(m);
473
474         if (reply)
475                 dbus_message_unref(reply);
476
477         if (session_fd >= 0)
478                 close_nointr_nofail(session_fd);
479
480         return r;
481 }
482
483 _public_ PAM_EXTERN int pam_sm_close_session(
484                 pam_handle_t *handle,
485                 int flags,
486                 int argc, const char **argv) {
487
488         const void *p = NULL, *existing = NULL;
489         const char *id;
490         DBusConnection *bus = NULL;
491         DBusMessage *m = NULL, *reply = NULL;
492         DBusError error;
493         int r;
494
495         assert(handle);
496
497         dbus_error_init(&error);
498
499         /* Only release session if it wasn't pre-existing when we
500          * tried to create it */
501         pam_get_data(handle, "systemd.existing", &existing);
502
503         id = pam_getenv(handle, "XDG_SESSION_ID");
504         if (id && !existing) {
505
506                 /* Before we go and close the FIFO we need to tell
507                  * logind that this is a clean session shutdown, so
508                  * that it doesn't just go and slaughter us
509                  * immediately after closing the fd */
510
511                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
512                 if (!bus) {
513                         pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
514                         r = PAM_SESSION_ERR;
515                         goto finish;
516                 }
517
518                 m = dbus_message_new_method_call(
519                                 "org.freedesktop.login1",
520                                 "/org/freedesktop/login1",
521                                 "org.freedesktop.login1.Manager",
522                                 "ReleaseSession");
523                 if (!m) {
524                         pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
525                         r = PAM_BUF_ERR;
526                         goto finish;
527                 }
528
529                 if (!dbus_message_append_args(m,
530                                               DBUS_TYPE_STRING, &id,
531                                               DBUS_TYPE_INVALID)) {
532                         pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
533                         r = PAM_BUF_ERR;
534                         goto finish;
535                 }
536
537                 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
538                 if (!reply) {
539                         pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
540                         r = PAM_SESSION_ERR;
541                         goto finish;
542                 }
543         }
544
545         r = PAM_SUCCESS;
546
547 finish:
548         pam_get_data(handle, "systemd.session-fd", &p);
549         if (p)
550                 close_nointr(PTR_TO_INT(p) - 1);
551
552         dbus_error_free(&error);
553
554         if (bus) {
555                 dbus_connection_close(bus);
556                 dbus_connection_unref(bus);
557         }
558
559         if (m)
560                 dbus_message_unref(m);
561
562         if (reply)
563                 dbus_message_unref(reply);
564
565         return r;
566 }