chiark / gitweb /
Rename systemd-logind to logind.
[elogind.git] / src / login / pam_elogind.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
28 #include <security/pam_modules.h>
29 #include <security/_pam_macros.h>
30 #include <security/pam_modutil.h>
31 #include <security/pam_ext.h>
32 #include <security/pam_misc.h>
33
34 #include "util.h"
35 #include "audit.h"
36 #include "macro.h"
37 #include "strv.h"
38 #include "bus-util.h"
39 #include "def.h"
40 #include "socket-util.h"
41 #include "fileio.h"
42 #include "bus-error.h"
43
44 static int parse_argv(
45                 pam_handle_t *handle,
46                 int argc, const char **argv,
47                 const char **class,
48                 const char **type,
49                 bool *debug) {
50
51         unsigned i;
52
53         assert(argc >= 0);
54         assert(argc == 0 || argv);
55
56         for (i = 0; i < (unsigned) argc; i++) {
57                 if (startswith(argv[i], "class=")) {
58                         if (class)
59                                 *class = argv[i] + 6;
60
61                 } else if (startswith(argv[i], "type=")) {
62                         if (type)
63                                 *type = argv[i] + 5;
64
65                 } else if (streq(argv[i], "debug")) {
66                         if (debug)
67                                 *debug = true;
68
69                 } else if (startswith(argv[i], "debug=")) {
70                         int k;
71
72                         k = parse_boolean(argv[i] + 6);
73                         if (k < 0)
74                                 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring.");
75                         else if (debug)
76                                 *debug = k;
77
78                 } else
79                         pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
80         }
81
82         return 0;
83 }
84
85 static int get_user_data(
86                 pam_handle_t *handle,
87                 const char **ret_username,
88                 struct passwd **ret_pw) {
89
90         const char *username = NULL;
91         struct passwd *pw = NULL;
92         int r;
93
94         assert(handle);
95         assert(ret_username);
96         assert(ret_pw);
97
98         r = pam_get_user(handle, &username, NULL);
99         if (r != PAM_SUCCESS) {
100                 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
101                 return r;
102         }
103
104         if (isempty(username)) {
105                 pam_syslog(handle, LOG_ERR, "User name not valid.");
106                 return PAM_AUTH_ERR;
107         }
108
109         pw = pam_modutil_getpwnam(handle, username);
110         if (!pw) {
111                 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
112                 return PAM_USER_UNKNOWN;
113         }
114
115         *ret_pw = pw;
116         *ret_username = username;
117
118         return PAM_SUCCESS;
119 }
120
121 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
122         union sockaddr_union sa = {
123                 .un.sun_family = AF_UNIX,
124         };
125         _cleanup_free_ char *p = NULL, *tty = NULL;
126         _cleanup_close_ int fd = -1;
127         struct ucred ucred;
128         int v, r;
129
130         assert(display);
131         assert(vtnr);
132
133         /* We deduce the X11 socket from the display name, then use
134          * SO_PEERCRED to determine the X11 server process, ask for
135          * the controlling tty of that and if it's a VC then we know
136          * the seat and the virtual terminal. Sounds ugly, is only
137          * semi-ugly. */
138
139         r = socket_from_display(display, &p);
140         if (r < 0)
141                 return r;
142         strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
143
144         fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
145         if (fd < 0)
146                 return -errno;
147
148         if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
149                 return -errno;
150
151         r = getpeercred(fd, &ucred);
152         if (r < 0)
153                 return r;
154
155         r = get_ctty(ucred.pid, NULL, &tty);
156         if (r < 0)
157                 return r;
158
159         v = vtnr_from_tty(tty);
160         if (v < 0)
161                 return v;
162         else if (v == 0)
163                 return -ENOENT;
164
165         if (seat)
166                 *seat = "seat0";
167         *vtnr = (uint32_t) v;
168
169         return 0;
170 }
171
172 static int export_legacy_dbus_address(
173                 pam_handle_t *handle,
174                 uid_t uid,
175                 const char *runtime) {
176
177 #ifdef ENABLE_KDBUS
178         _cleanup_free_ char *s = NULL;
179         int r;
180
181         /* skip export if kdbus is not active */
182         if (access("/sys/fs/kdbus", F_OK) < 0)
183                 return PAM_SUCCESS;
184
185         if (asprintf(&s, KERNEL_USER_BUS_ADDRESS_FMT ";" UNIX_USER_BUS_ADDRESS_FMT, uid, runtime) < 0) {
186                 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
187                 return PAM_BUF_ERR;
188         }
189
190         r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", s, 0);
191         if (r != PAM_SUCCESS) {
192                 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
193                 return r;
194         }
195 #endif
196         return PAM_SUCCESS;
197 }
198
199 _public_ PAM_EXTERN int pam_sm_open_session(
200                 pam_handle_t *handle,
201                 int flags,
202                 int argc, const char **argv) {
203
204         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
205         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
206         const char
207                 *username, *id, *object_path, *runtime_path,
208                 *service = NULL,
209                 *tty = NULL, *display = NULL,
210                 *remote_user = NULL, *remote_host = NULL,
211                 *seat = NULL,
212                 *type = NULL, *class = NULL,
213                 *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL;
214         _cleanup_bus_close_unref_ sd_bus *bus = NULL;
215         int session_fd = -1, existing, r;
216         bool debug = false, remote;
217         struct passwd *pw;
218         uint32_t vtnr = 0;
219         uid_t original_uid;
220
221         assert(handle);
222
223         /* Make this a NOP on non-logind systems */
224         if (!logind_running())
225                 return PAM_SUCCESS;
226
227         if (parse_argv(handle,
228                        argc, argv,
229                        &class_pam,
230                        &type_pam,
231                        &debug) < 0)
232                 return PAM_SESSION_ERR;
233
234         if (debug)
235                 pam_syslog(handle, LOG_DEBUG, "pam-systemd initializing");
236
237         r = get_user_data(handle, &username, &pw);
238         if (r != PAM_SUCCESS) {
239                 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
240                 return r;
241         }
242
243         /* Make sure we don't enter a loop by talking to
244          * logind when it is actually waiting for the
245          * background to finish start-up. If the service is
246          * "systemd-user" we simply set XDG_RUNTIME_DIR and
247          * leave. */
248
249         pam_get_item(handle, PAM_SERVICE, (const void**) &service);
250         if (streq_ptr(service, "systemd-user")) {
251                 _cleanup_free_ char *p = NULL, *rt = NULL;
252
253                 if (asprintf(&p, "/run/systemd/users/"UID_FMT, pw->pw_uid) < 0)
254                         return PAM_BUF_ERR;
255
256                 r = parse_env_file(p, NEWLINE,
257                                    "RUNTIME", &rt,
258                                    NULL);
259                 if (r < 0 && r != -ENOENT)
260                         return PAM_SESSION_ERR;
261
262                 if (rt)  {
263                         r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
264                         if (r != PAM_SUCCESS) {
265                                 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
266                                 return r;
267                         }
268
269                         r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
270                         if (r != PAM_SUCCESS)
271                                 return r;
272                 }
273
274                 return PAM_SUCCESS;
275         }
276
277         /* Otherwise, we ask logind to create a session for us */
278
279         pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
280         pam_get_item(handle, PAM_TTY, (const void**) &tty);
281         pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
282         pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
283
284         seat = pam_getenv(handle, "XDG_SEAT");
285         if (isempty(seat))
286                 seat = getenv("XDG_SEAT");
287
288         cvtnr = pam_getenv(handle, "XDG_VTNR");
289         if (isempty(cvtnr))
290                 cvtnr = getenv("XDG_VTNR");
291
292         type = pam_getenv(handle, "XDG_SESSION_TYPE");
293         if (isempty(type))
294                 type = getenv("XDG_SESSION_TYPE");
295         if (isempty(type))
296                 type = type_pam;
297
298         class = pam_getenv(handle, "XDG_SESSION_CLASS");
299         if (isempty(class))
300                 class = getenv("XDG_SESSION_CLASS");
301         if (isempty(class))
302                 class = class_pam;
303
304         desktop = pam_getenv(handle, "XDG_SESSION_DESKTOP");
305         if (isempty(desktop))
306                 desktop = getenv("XDG_SESSION_DESKTOP");
307
308         tty = strempty(tty);
309
310         if (strchr(tty, ':')) {
311                 /* A tty with a colon is usually an X11 display,
312                  * placed there to show up in utmp. We rearrange
313                  * things and don't pretend that an X display was a
314                  * tty. */
315
316                 if (isempty(display))
317                         display = tty;
318                 tty = NULL;
319         } else if (streq(tty, "cron")) {
320                 /* cron has been setting PAM_TTY to "cron" for a very
321                  * long time and it probably shouldn't stop doing that
322                  * for compatibility reasons. */
323                 type = "unspecified";
324                 class = "background";
325                 tty = NULL;
326         } else if (streq(tty, "ssh")) {
327                 /* ssh has been setting PAM_TTY to "ssh" for a very
328                  * long time and probably shouldn't stop doing that
329                  * for compatibility reasons. */
330                 type ="tty";
331                 class = "user";
332                 tty = NULL;
333         }
334
335         /* If this fails vtnr will be 0, that's intended */
336         if (!isempty(cvtnr))
337                 safe_atou32(cvtnr, &vtnr);
338
339         if (!isempty(display) && !vtnr) {
340                 if (isempty(seat))
341                         get_seat_from_display(display, &seat, &vtnr);
342                 else if (streq(seat, "seat0"))
343                         get_seat_from_display(display, NULL, &vtnr);
344         }
345
346         if (seat && !streq(seat, "seat0") && vtnr != 0) {
347                 pam_syslog(handle, LOG_DEBUG, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat);
348                 vtnr = 0;
349         }
350
351         if (isempty(type))
352                 type = !isempty(display) ? "x11" :
353                            !isempty(tty) ? "tty" : "unspecified";
354
355         if (isempty(class))
356                 class = streq(type, "unspecified") ? "background" : "user";
357
358         remote = !isempty(remote_host) && !is_localhost(remote_host);
359
360         /* Talk to logind over the message bus */
361
362         r = sd_bus_open_system(&bus);
363         if (r < 0) {
364                 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
365                 return PAM_SESSION_ERR;
366         }
367
368         if (debug)
369                 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
370                            "uid="UID_FMT" pid="PID_FMT" service=%s type=%s class=%s desktop=%s seat=%s vtnr=%"PRIu32" tty=%s display=%s remote=%s remote_user=%s remote_host=%s",
371                            pw->pw_uid, getpid(),
372                            strempty(service),
373                            type, class, strempty(desktop),
374                            strempty(seat), vtnr, strempty(tty), strempty(display),
375                            yes_no(remote), strempty(remote_user), strempty(remote_host));
376
377         r = sd_bus_call_method(bus,
378                                "org.freedesktop.login1",
379                                "/org/freedesktop/login1",
380                                "org.freedesktop.login1.Manager",
381                                "CreateSession",
382                                &error,
383                                &reply,
384                                "uusssssussbssa(sv)",
385                                (uint32_t) pw->pw_uid,
386                                (uint32_t) getpid(),
387                                service,
388                                type,
389                                class,
390                                desktop,
391                                seat,
392                                vtnr,
393                                tty,
394                                display,
395                                remote,
396                                remote_user,
397                                remote_host,
398                                0);
399         if (r < 0) {
400                 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
401                 return PAM_SYSTEM_ERR;
402         }
403
404         r = sd_bus_message_read(reply,
405                                 "soshusub",
406                                 &id,
407                                 &object_path,
408                                 &runtime_path,
409                                 &session_fd,
410                                 &original_uid,
411                                 &seat,
412                                 &vtnr,
413                                 &existing);
414         if (r < 0) {
415                 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
416                 return PAM_SESSION_ERR;
417         }
418
419         if (debug)
420                 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
421                            "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
422                            id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
423
424         r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
425         if (r != PAM_SUCCESS) {
426                 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
427                 return r;
428         }
429
430         if (original_uid == pw->pw_uid) {
431                 /* Don't set $XDG_RUNTIME_DIR if the user we now
432                  * authenticated for does not match the original user
433                  * of the session. We do this in order not to result
434                  * in privileged apps clobbering the runtime directory
435                  * unnecessarily. */
436
437                 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
438                 if (r != PAM_SUCCESS) {
439                         pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
440                         return r;
441                 }
442
443                 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
444                 if (r != PAM_SUCCESS)
445                         return r;
446         }
447
448         if (!isempty(seat)) {
449                 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
450                 if (r != PAM_SUCCESS) {
451                         pam_syslog(handle, LOG_ERR, "Failed to set seat.");
452                         return r;
453                 }
454         }
455
456         if (vtnr > 0) {
457                 char buf[DECIMAL_STR_MAX(vtnr)];
458                 sprintf(buf, "%u", vtnr);
459
460                 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
461                 if (r != PAM_SUCCESS) {
462                         pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
463                         return r;
464                 }
465         }
466
467         r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
468         if (r != PAM_SUCCESS) {
469                 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
470                 return r;
471         }
472
473         if (session_fd >= 0) {
474                 session_fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3);
475                 if (session_fd < 0) {
476                         pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
477                         return PAM_SESSION_ERR;
478                 }
479
480                 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
481                 if (r != PAM_SUCCESS) {
482                         pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
483                         safe_close(session_fd);
484                         return r;
485                 }
486         }
487
488         return PAM_SUCCESS;
489 }
490
491 _public_ PAM_EXTERN int pam_sm_close_session(
492                 pam_handle_t *handle,
493                 int flags,
494                 int argc, const char **argv) {
495
496         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
497         _cleanup_bus_close_unref_ sd_bus *bus = NULL;
498         const void *existing = NULL;
499         const char *id;
500         int r;
501
502         assert(handle);
503
504         /* Only release session if it wasn't pre-existing when we
505          * tried to create it */
506         pam_get_data(handle, "systemd.existing", &existing);
507
508         id = pam_getenv(handle, "XDG_SESSION_ID");
509         if (id && !existing) {
510
511                 /* Before we go and close the FIFO we need to tell
512                  * logind that this is a clean session shutdown, so
513                  * that it doesn't just go and slaughter us
514                  * immediately after closing the fd */
515
516                 r = sd_bus_open_system(&bus);
517                 if (r < 0) {
518                         pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
519                         return PAM_SESSION_ERR;
520                 }
521
522                 r = sd_bus_call_method(bus,
523                                        "org.freedesktop.login1",
524                                        "/org/freedesktop/login1",
525                                        "org.freedesktop.login1.Manager",
526                                        "ReleaseSession",
527                                        &error,
528                                        NULL,
529                                        "s",
530                                        id);
531                 if (r < 0) {
532                         pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error, r));
533                         return PAM_SESSION_ERR;
534                 }
535         }
536
537         /* Note that we are knowingly leaking the FIFO fd here. This
538          * way, logind can watch us die. If we closed it here it would
539          * not have any clue when that is completed. Given that one
540          * cannot really have multiple PAM sessions open from the same
541          * process this means we will leak one FD at max. */
542
543         return PAM_SUCCESS;
544 }