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