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