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