chiark / gitweb /
pam_systemd: dup the fd received from logind
[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 "bus-util.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                 if (startswith(argv[i], "class=")) {
56                         if (class)
57                                 *class = argv[i] + 6;
58
59                 } else if (streq(argv[i], "debug")) {
60                         if (debug)
61                                 *debug = true;
62
63                 } else if (startswith(argv[i], "debug=")) {
64                         int k;
65
66                         k = parse_boolean(argv[i] + 6);
67                         if (k < 0)
68                                 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring.");
69                         else if (debug)
70                                 *debug = k;
71
72                 } else
73                         pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
74
75         return 0;
76 }
77
78 static int get_user_data(
79                 pam_handle_t *handle,
80                 const char **ret_username,
81                 struct passwd **ret_pw) {
82
83         const char *username = NULL;
84         struct passwd *pw = NULL;
85         uid_t uid;
86         int r;
87
88         assert(handle);
89         assert(ret_username);
90         assert(ret_pw);
91
92         r = audit_loginuid_from_pid(0, &uid);
93         if (r >= 0)
94                 pw = pam_modutil_getpwuid(handle, uid);
95         else {
96                 r = pam_get_user(handle, &username, NULL);
97                 if (r != PAM_SUCCESS) {
98                         pam_syslog(handle, LOG_ERR, "Failed to get user name.");
99                         return r;
100                 }
101
102                 if (isempty(username)) {
103                         pam_syslog(handle, LOG_ERR, "User name not valid.");
104                         return PAM_AUTH_ERR;
105                 }
106
107                 pw = pam_modutil_getpwnam(handle, username);
108         }
109
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 ? username : pw->pw_name;
117
118         return PAM_SUCCESS;
119 }
120
121 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
122         _cleanup_free_ char *p = NULL;
123         int r;
124         _cleanup_close_ int fd = -1;
125         union sockaddr_union sa = {
126                 .un.sun_family = AF_UNIX,
127         };
128         struct ucred ucred;
129         socklen_t l;
130         _cleanup_free_ char *tty = NULL;
131         int v;
132
133         assert(display);
134         assert(vtnr);
135
136         /* We deduce the X11 socket from the display name, then use
137          * SO_PEERCRED to determine the X11 server process, ask for
138          * the controlling tty of that and if it's a VC then we know
139          * the seat and the virtual terminal. Sounds ugly, is only
140          * semi-ugly. */
141
142         r = socket_from_display(display, &p);
143         if (r < 0)
144                 return r;
145         strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
146
147         fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
148         if (fd < 0)
149                 return -errno;
150
151         if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
152                 return -errno;
153
154         l = sizeof(ucred);
155         r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l);
156         if (r < 0)
157                 return -errno;
158
159         r = get_ctty(ucred.pid, NULL, &tty);
160         if (r < 0)
161                 return r;
162
163         v = vtnr_from_tty(tty);
164         if (v < 0)
165                 return v;
166         else if (v == 0)
167                 return -ENOENT;
168
169         if (seat)
170                 *seat = "seat0";
171         *vtnr = (uint32_t) v;
172
173         return 0;
174 }
175
176 _public_ PAM_EXTERN int pam_sm_open_session(
177                 pam_handle_t *handle,
178                 int flags,
179                 int argc, const char **argv) {
180
181         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
182         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
183         const char
184                 *username, *id, *object_path, *runtime_path,
185                 *service = NULL,
186                 *tty = NULL, *display = NULL,
187                 *remote_user = NULL, *remote_host = NULL,
188                 *seat = NULL,
189                 *type = NULL, *class = NULL,
190                 *class_pam = NULL, *cvtnr = NULL;
191         _cleanup_bus_unref_ sd_bus *bus = NULL;
192         int session_fd = -1, existing, r;
193         uint32_t uid, pid, vtnr = 0;
194         bool debug = false, remote;
195         struct passwd *pw;
196
197         assert(handle);
198
199         if (debug)
200                 pam_syslog(handle, LOG_INFO, "pam-systemd initializing");
201
202         /* Make this a NOP on non-logind systems */
203         if (!logind_running())
204                 return PAM_SUCCESS;
205
206         if (parse_argv(handle,
207                        argc, argv,
208                        &class_pam,
209                        &debug) < 0)
210                 return PAM_SESSION_ERR;
211
212         r = get_user_data(handle, &username, &pw);
213         if (r != PAM_SUCCESS) {
214                 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
215                 return r;
216         }
217
218         /* Make sure we don't enter a loop by talking to
219          * systemd-logind when it is actually waiting for the
220          * background to finish start-up. If the service is
221          * "systemd-user" we simply set XDG_RUNTIME_DIR and
222          * leave. */
223
224         pam_get_item(handle, PAM_SERVICE, (const void**) &service);
225         if (streq_ptr(service, "systemd-user")) {
226                 _cleanup_free_ char *p = NULL, *rt = NULL;
227
228                 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0)
229                         return PAM_BUF_ERR;
230
231                 r = parse_env_file(p, NEWLINE,
232                                    "RUNTIME", &rt,
233                                    NULL);
234                 if (r < 0 && r != -ENOENT)
235                         return PAM_SESSION_ERR;
236
237                 if (rt)  {
238                         r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
239                         if (r != PAM_SUCCESS) {
240                                 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
241                                 return r;
242                         }
243                 }
244
245                 return PAM_SUCCESS;
246         }
247
248         /* Otherwise, we ask logind to create a session for us */
249
250         uid = pw->pw_uid;
251         pid = getpid();
252
253         pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
254         pam_get_item(handle, PAM_TTY, (const void**) &tty);
255         pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
256         pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
257
258         seat = pam_getenv(handle, "XDG_SEAT");
259         if (isempty(seat))
260                 seat = getenv("XDG_SEAT");
261
262         cvtnr = pam_getenv(handle, "XDG_VTNR");
263         if (isempty(cvtnr))
264                 cvtnr = getenv("XDG_VTNR");
265
266         service = strempty(service);
267         tty = strempty(tty);
268         display = strempty(display);
269         remote_user = strempty(remote_user);
270         remote_host = strempty(remote_host);
271         seat = strempty(seat);
272
273         if (strchr(tty, ':')) {
274                 /* A tty with a colon is usually an X11 display,
275                  * placed there to show up in utmp. We rearrange
276                  * things and don't pretend that an X display was a
277                  * tty. */
278
279                 if (isempty(display))
280                         display = tty;
281                 tty = "";
282         } else if (streq(tty, "cron")) {
283                 /* cron has been setting PAM_TTY to "cron" for a very
284                  * long time and it probably shouldn't stop doing that
285                  * for compatibility reasons. */
286                 tty = "";
287                 type = "unspecified";
288         } else if (streq(tty, "ssh")) {
289                 /* ssh has been setting PAM_TTY to "ssh" for a very
290                  * long time and probably shouldn't stop doing that
291                  * for compatibility reasons. */
292                 tty = "";
293                 type ="tty";
294         }
295
296         /* If this fails vtnr will be 0, that's intended */
297         if (!isempty(cvtnr))
298                 safe_atou32(cvtnr, &vtnr);
299
300         if (!isempty(display) && vtnr <= 0) {
301                 if (isempty(seat))
302                         get_seat_from_display(display, &seat, &vtnr);
303                 else if (streq(seat, "seat0"))
304                         get_seat_from_display(display, NULL, &vtnr);
305         }
306
307         if (!type)
308                 type = !isempty(display) ? "x11" :
309                         !isempty(tty) ? "tty" : "unspecified";
310
311         class = pam_getenv(handle, "XDG_SESSION_CLASS");
312         if (isempty(class))
313                 class = getenv("XDG_SESSION_CLASS");
314         if (isempty(class))
315                 class = class_pam;
316         if (isempty(class))
317                 class = streq(type, "unspecified") ? "background" : "user";
318
319         remote = !isempty(remote_host) &&
320                 !streq(remote_host, "localhost") &&
321                 !streq(remote_host, "localhost.localdomain");
322
323         /* Talk to logind over the message bus */
324
325         r = sd_bus_open_system(&bus);
326         if (r < 0) {
327                 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
328                 return PAM_SESSION_ERR;
329         }
330
331         if (debug)
332                 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
333                            "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",
334                            uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
335
336         r = sd_bus_call_method(bus,
337                                "org.freedesktop.login1",
338                                "/org/freedesktop/login1",
339                                "org.freedesktop.login1.Manager",
340                                "CreateSession",
341                                &error,
342                                &reply,
343                                "uussssussbssa(sv)",
344                                uid,
345                                pid,
346                                service,
347                                type,
348                                class,
349                                seat,
350                                vtnr,
351                                tty,
352                                display,
353                                remote,
354                                remote_user,
355                                remote_host,
356                                0);
357         if (r < 0) {
358                 pam_syslog(handle, LOG_ERR, "Failed to communicate with systemd-logind: %s", strerror(-r));
359                 if (error.name || error.message)
360                         pam_syslog(handle, LOG_ERR, "systemd-logind returned %s: %s",
361                                    error.name ?: "unknown error",
362                                    error.message ?: "no message");
363                 return PAM_SYSTEM_ERR;
364         }
365
366         r = sd_bus_message_read(reply,
367                                 "soshsub",
368                                 &id,
369                                 &object_path,
370                                 &runtime_path,
371                                 &session_fd,
372                                 &seat,
373                                 &vtnr,
374                                 &existing);
375         if (r < 0) {
376                 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
377                 return PAM_SESSION_ERR;
378         }
379
380         if (debug)
381                 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
382                            "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
383                            id, object_path, runtime_path, session_fd, seat, vtnr);
384
385         r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
386         if (r != PAM_SUCCESS) {
387                 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
388                 return r;
389         }
390
391         r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
392         if (r != PAM_SUCCESS) {
393                 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
394                 return r;
395         }
396
397         if (!isempty(seat)) {
398                 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
399                 if (r != PAM_SUCCESS) {
400                         pam_syslog(handle, LOG_ERR, "Failed to set seat.");
401                         return r;
402                 }
403         }
404
405         if (vtnr > 0) {
406                 char buf[11];
407                 snprintf(buf, sizeof(buf), "%u", vtnr);
408                 char_array_0(buf);
409
410                 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
411                 if (r != PAM_SUCCESS) {
412                         pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
413                         return r;
414                 }
415         }
416
417         r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
418         if (r != PAM_SUCCESS) {
419                 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
420                 return r;
421         }
422
423         if (session_fd >= 0) {
424                 session_fd = dup(session_fd);
425                 if (session_fd < 0) {
426                         pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
427                         return PAM_SESSION_ERR;
428                 }
429
430                 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
431                 if (r != PAM_SUCCESS) {
432                         pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
433                         close_nointr_nofail(session_fd);
434                         return r;
435                 }
436         }
437
438         return PAM_SUCCESS;
439 }
440
441 _public_ PAM_EXTERN int pam_sm_close_session(
442                 pam_handle_t *handle,
443                 int flags,
444                 int argc, const char **argv) {
445
446         const void *p = NULL, *existing = NULL;
447         const char *id;
448         int r;
449
450         _cleanup_bus_unref_ sd_bus *bus = NULL;
451         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
452         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
453
454         assert(handle);
455
456         /* Only release session if it wasn't pre-existing when we
457          * tried to create it */
458         pam_get_data(handle, "systemd.existing", &existing);
459
460         id = pam_getenv(handle, "XDG_SESSION_ID");
461         if (id && !existing) {
462
463                 /* Before we go and close the FIFO we need to tell
464                  * logind that this is a clean session shutdown, so
465                  * that it doesn't just go and slaughter us
466                  * immediately after closing the fd */
467
468                 r = sd_bus_open_system(&bus);
469                 if (r < 0) {
470                         pam_syslog(handle, LOG_ERR,
471                                   "Failed to connect to system bus: %s", strerror(-r));
472                         r = PAM_SESSION_ERR;
473                         goto finish;
474                 }
475
476                 r = sd_bus_call_method(bus,
477                                        "org.freedesktop.login1",
478                                        "/org/freedesktop/login1",
479                                        "org.freedesktop.login1.Manager",
480                                        "ReleaseSession",
481                                        &error,
482                                        NULL,
483                                        "s",
484                                        id);
485                 if (r < 0) {
486                         pam_syslog(handle, LOG_ERR,
487                                    "Failed to release session: %s", strerror(-r));
488                         if (error.name || error.message)
489                                 pam_syslog(handle, LOG_ERR, "systemd-logind returned %s: %s",
490                                            error.name ?: "unknown error",
491                                            error.message ?: "no message");
492
493                         r = PAM_SESSION_ERR;
494                         goto finish;
495                 }
496         }
497
498         r = PAM_SUCCESS;
499
500 finish:
501         pam_get_data(handle, "systemd.session-fd", &p);
502         if (p)
503                 close_nointr(PTR_TO_INT(p) - 1);
504
505         return r;
506 }