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