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