chiark / gitweb /
activate: port to use new getpeername_pretty()/getsockname_pretty() calls
[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 bus */
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 }