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