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