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