chiark / gitweb /
0be39de95ab8168310f6f9fab8844a2ce224a92b
[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                 r = PAM_SESSION_ERR;
211                 goto finish;
212         }
213
214         r = get_user_data(handle, &username, &pw);
215         if (r != PAM_SUCCESS)
216                 goto finish;
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         r = sd_bus_open_system(&bus);
325         if (r < 0) {
326                 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
327                 return PAM_SESSION_ERR;
328         }
329
330         if (debug)
331                 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
332                            "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",
333                            uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
334
335         r = sd_bus_call_method(bus,
336                                "org.freedesktop.login1",
337                                "/org/freedesktop/login1",
338                                "org.freedesktop.login1.Manager",
339                                "CreateSession",
340                                &error,
341                                &reply,
342                                "uussssussbssa(sv)",
343                                uid,
344                                pid,
345                                service,
346                                type,
347                                class,
348                                seat,
349                                vtnr,
350                                tty,
351                                display,
352                                remote,
353                                remote_user,
354                                remote_host,
355                                0);
356         if (r < 0) {
357                 pam_syslog(handle, LOG_ERR, "Failed to communicate with systemd-logind: %s", strerror(-r));
358                 if (error.name || error.message)
359                         pam_syslog(handle, LOG_ERR, "systemd-logind returned %s: %s",
360                                    error.name ?: "unknown error",
361                                    error.message ?: "no message");
362                 return PAM_SYSTEM_ERR;
363         }
364
365         r = sd_bus_message_read(reply,
366                                 "soshsub",
367                                 &id,
368                                 &object_path,
369                                 &runtime_path,
370                                 &session_fd,
371                                 &seat,
372                                 &vtnr,
373                                 &existing);
374         if (r < 0) {
375                 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
376                 r = PAM_SESSION_ERR;
377                 goto finish;
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                 goto finish;
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                 goto finish;
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                         goto finish;
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                         goto finish;
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                 goto finish;
421         }
422
423         if (session_fd >= 0) {
424                 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
425                 if (r != PAM_SUCCESS) {
426                         pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
427                         goto finish;
428                 }
429         }
430
431         return PAM_SUCCESS;
432
433 finish:
434         if (session_fd >= 0)
435                 close_nointr_nofail(session_fd);
436
437         return r;
438 }
439
440 _public_ PAM_EXTERN int pam_sm_close_session(
441                 pam_handle_t *handle,
442                 int flags,
443                 int argc, const char **argv) {
444
445         const void *p = NULL, *existing = NULL;
446         const char *id;
447         int r;
448
449         _cleanup_bus_unref_ sd_bus *bus = NULL;
450         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
451         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
452
453         assert(handle);
454
455         /* Only release session if it wasn't pre-existing when we
456          * tried to create it */
457         pam_get_data(handle, "systemd.existing", &existing);
458
459         id = pam_getenv(handle, "XDG_SESSION_ID");
460         if (id && !existing) {
461
462                 /* Before we go and close the FIFO we need to tell
463                  * logind that this is a clean session shutdown, so
464                  * that it doesn't just go and slaughter us
465                  * immediately after closing the fd */
466
467                 r = sd_bus_open_system(&bus);
468                 if (r < 0) {
469                         pam_syslog(handle, LOG_ERR,
470                                   "Failed to connect to system bus: %s", strerror(-r));
471                         r = PAM_SESSION_ERR;
472                         goto finish;
473                 }
474
475                 r = sd_bus_call_method(bus,
476                                        "org.freedesktop.login1",
477                                        "/org/freedesktop/login1",
478                                        "org.freedesktop.login1.Manager",
479                                        "ReleaseSession",
480                                        &error,
481                                        NULL,
482                                        "s",
483                                        id);
484                 if (r < 0) {
485                         pam_syslog(handle, LOG_ERR,
486                                    "Failed to release session: %s", strerror(-r));
487                         if (error.name || error.message)
488                                 pam_syslog(handle, LOG_ERR, "systemd-logind returned %s: %s",
489                                            error.name ?: "unknown error",
490                                            error.message ?: "no message");
491
492                         r = PAM_SESSION_ERR;
493                         goto finish;
494                 }
495         }
496
497         r = PAM_SUCCESS;
498
499 finish:
500         pam_get_data(handle, "systemd.session-fd", &p);
501         if (p)
502                 close_nointr(PTR_TO_INT(p) - 1);
503
504         return r;
505 }