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