]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
8c6db833 | 2 | |
00229fe4 | 3 | #include <endian.h> |
8c6db833 | 4 | #include <fcntl.h> |
8c6db833 | 5 | #include <pwd.h> |
8c6db833 | 6 | #include <security/_pam_macros.h> |
8c6db833 LP |
7 | #include <security/pam_ext.h> |
8 | #include <security/pam_misc.h> | |
00229fe4 LP |
9 | #include <security/pam_modules.h> |
10 | #include <security/pam_modutil.h> | |
11 | #include <sys/file.h> | |
6ad1d1ed | 12 | #include "time-util.h" |
ca78ad1d | 13 | #include <sys/stat.h> |
e21d9060 | 14 | #include <sys/sysmacros.h> |
ca78ad1d | 15 | #include <unistd.h> |
8c6db833 | 16 | |
4582f8d7 LP |
17 | #include "sd-bus.h" |
18 | #include "sd-varlink.h" | |
19 | ||
b5efdb8a | 20 | #include "alloc-util.h" |
00229fe4 LP |
21 | #include "bus-common-errors.h" |
22 | #include "bus-error.h" | |
47094ce0 | 23 | #include "bus-internal.h" |
9b71e4ab | 24 | #include "bus-locator.h" |
bf1b9ae4 LP |
25 | #include "cap-list.h" |
26 | #include "capability-util.h" | |
fdb3deca | 27 | #include "cgroup-setup.h" |
c747c041 | 28 | #include "chase.h" |
229d4a98 | 29 | #include "creds-util.h" |
3a4e4ffa | 30 | #include "devnum-util.h" |
4bbccb02 | 31 | #include "errno-util.h" |
6553db60 | 32 | #include "extract-word.h" |
3ffd4af2 | 33 | #include "fd-util.h" |
f97b34a6 | 34 | #include "format-util.h" |
f9c1f4e1 | 35 | #include "fs-util.h" |
958b66ea | 36 | #include "hostname-util.h" |
d8069b8a | 37 | #include "io-util.h" |
4582f8d7 | 38 | #include "json-util.h" |
f9c1f4e1 | 39 | #include "locale-util.h" |
00229fe4 | 40 | #include "login-util.h" |
d8069b8a | 41 | #include "osc-context.h" |
d750dde2 | 42 | #include "pam-util.h" |
6bedfcbb | 43 | #include "parse-util.h" |
e37e5ed3 | 44 | #include "path-util.h" |
ed5033fd | 45 | #include "percent-util.h" |
6ad1d1ed DDM |
46 | #include "pidfd-util.h" |
47 | #include "pidref.h" | |
df0ff127 | 48 | #include "process-util.h" |
f9c1f4e1 | 49 | #include "rlimit-util.h" |
00229fe4 | 50 | #include "socket-util.h" |
055c08ef | 51 | #include "stdio-util.h" |
6ad1d1ed | 52 | #include "string-util.h" |
00229fe4 LP |
53 | #include "strv.h" |
54 | #include "terminal-util.h" | |
cfb7abc7 | 55 | #include "tmpfile-util.h" |
9ab0d3eb LP |
56 | #include "user-util.h" |
57 | #include "userdb.h" | |
8c6db833 | 58 | |
fbcb6300 LP |
59 | #define LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE) |
60 | ||
bf1b9ae4 LP |
61 | static int parse_caps( |
62 | pam_handle_t *handle, | |
63 | const char *value, | |
64 | uint64_t *caps) { | |
65 | ||
66 | bool subtract; | |
67 | int r; | |
68 | ||
69 | assert(handle); | |
70 | assert(value); | |
71 | ||
72 | if (value[0] == '~') { | |
73 | subtract = true; | |
74 | value++; | |
75 | } else | |
76 | subtract = false; | |
77 | ||
78 | for (;;) { | |
79 | _cleanup_free_ char *s = NULL; | |
80 | uint64_t b, m; | |
81 | int c; | |
82 | ||
83 | /* We can't use spaces as separators here, as PAM's simplistic argument parser doesn't allow | |
84 | * spaces inside of arguments. We use commas instead (which is similar to cap_from_text(), | |
85 | * which also uses commas). */ | |
86 | r = extract_first_word(&value, &s, ",", EXTRACT_DONT_COALESCE_SEPARATORS); | |
87 | if (r < 0) | |
88 | return r; | |
89 | if (r == 0) | |
90 | break; | |
91 | ||
92 | c = capability_from_name(s); | |
93 | if (c < 0) { | |
94 | pam_syslog(handle, LOG_WARNING, "Unknown capability, ignoring: %s", s); | |
95 | continue; | |
96 | } | |
97 | ||
98 | m = UINT64_C(1) << c; | |
99 | ||
100 | if (!caps) | |
101 | continue; | |
102 | ||
103 | if (*caps == UINT64_MAX) | |
104 | b = subtract ? all_capabilities() : 0; | |
105 | else | |
106 | b = *caps; | |
107 | ||
108 | if (subtract) | |
109 | *caps = b & ~m; | |
110 | else | |
111 | *caps = b | m; | |
112 | } | |
113 | ||
114 | return 0; | |
115 | } | |
116 | ||
baae0358 LP |
117 | static int parse_argv( |
118 | pam_handle_t *handle, | |
119 | int argc, const char **argv, | |
120 | const char **class, | |
49ebd11f | 121 | const char **type, |
f5cb2820 | 122 | const char **desktop, |
c747c041 | 123 | const char **area, |
bf1b9ae4 LP |
124 | bool *debug, |
125 | uint64_t *default_capability_bounding_set, | |
126 | uint64_t *default_capability_ambient_set) { | |
8c6db833 | 127 | |
bf1b9ae4 | 128 | int r; |
8c6db833 | 129 | |
bf1b9ae4 | 130 | assert(handle); |
8c6db833 LP |
131 | assert(argc >= 0); |
132 | assert(argc == 0 || argv); | |
133 | ||
bf1b9ae4 | 134 | for (int i = 0; i < argc; i++) { |
d9608d40 LP |
135 | const char *p; |
136 | ||
137 | if ((p = startswith(argv[i], "class="))) { | |
485507b8 | 138 | if (class) |
d9608d40 | 139 | *class = p; |
485507b8 | 140 | |
d9608d40 | 141 | } else if ((p = startswith(argv[i], "type="))) { |
49ebd11f | 142 | if (type) |
d9608d40 | 143 | *type = p; |
49ebd11f | 144 | |
d9608d40 | 145 | } else if ((p = startswith(argv[i], "desktop="))) { |
f5cb2820 | 146 | if (desktop) |
d9608d40 | 147 | *desktop = p; |
f5cb2820 | 148 | |
c747c041 LP |
149 | } else if ((p = startswith(argv[i], "area="))) { |
150 | ||
151 | if (!isempty(p) && !filename_is_valid(p)) | |
152 | pam_syslog(handle, LOG_WARNING, "Area name specified among PAM module parameters is not valid, ignoring: %m"); | |
153 | else if (area) | |
154 | *area = p; | |
155 | ||
05a049cc ZJS |
156 | } else if (streq(argv[i], "debug")) { |
157 | if (debug) | |
158 | *debug = true; | |
fb6becb4 | 159 | |
d9608d40 | 160 | } else if ((p = startswith(argv[i], "debug="))) { |
bf1b9ae4 LP |
161 | r = parse_boolean(p); |
162 | if (r < 0) | |
d9608d40 | 163 | pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", p); |
05a049cc | 164 | else if (debug) |
bf1b9ae4 LP |
165 | *debug = r; |
166 | ||
167 | } else if ((p = startswith(argv[i], "default-capability-bounding-set="))) { | |
168 | r = parse_caps(handle, p, default_capability_bounding_set); | |
169 | if (r < 0) | |
170 | pam_syslog(handle, LOG_WARNING, "Failed to parse default-capability-bounding-set= argument, ignoring: %s", p); | |
171 | ||
172 | } else if ((p = startswith(argv[i], "default-capability-ambient-set="))) { | |
173 | r = parse_caps(handle, p, default_capability_ambient_set); | |
174 | if (r < 0) | |
175 | pam_syslog(handle, LOG_WARNING, "Failed to parse default-capability-ambient-set= argument, ignoring: %s", p); | |
0e318cad | 176 | |
05a049cc | 177 | } else |
bf1b9ae4 | 178 | pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); |
49ebd11f | 179 | } |
8c6db833 | 180 | |
8c6db833 LP |
181 | return 0; |
182 | } | |
183 | ||
9ab0d3eb | 184 | static int acquire_user_record( |
8c6db833 | 185 | pam_handle_t *handle, |
9ab0d3eb | 186 | UserRecord **ret_record) { |
8c6db833 | 187 | |
8c6db833 LP |
188 | int r; |
189 | ||
190 | assert(handle); | |
8c6db833 | 191 | |
a642f9d2 | 192 | const char *username = NULL; |
baae0358 | 193 | r = pam_get_user(handle, &username, NULL); |
f48e9376 ZJS |
194 | if (r != PAM_SUCCESS) |
195 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get user name: @PAMERR@"); | |
f48e9376 ZJS |
196 | if (isempty(username)) |
197 | return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, "User name not valid."); | |
8c6db833 | 198 | |
dbe7fff4 | 199 | /* If pam_systemd_homed (or some other module) already acquired the user record we can reuse it |
9ab0d3eb | 200 | * here. */ |
a642f9d2 | 201 | _cleanup_free_ char *field = strjoin("systemd-user-record-", username); |
dbe7fff4 LP |
202 | if (!field) |
203 | return pam_log_oom(handle); | |
9ab0d3eb | 204 | |
a642f9d2 LP |
205 | _cleanup_(user_record_unrefp) UserRecord *ur = NULL; |
206 | const char *json = NULL; | |
dbe7fff4 | 207 | r = pam_get_data(handle, field, (const void**) &json); |
f48e9376 ZJS |
208 | if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) |
209 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM user record data: @PAMERR@"); | |
dbe7fff4 | 210 | if (r == PAM_SUCCESS && json) { |
309a747f | 211 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; |
9ab0d3eb LP |
212 | |
213 | /* Parse cached record */ | |
309a747f | 214 | r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE, &v, NULL, NULL); |
7e7b53b4 ZJS |
215 | if (r < 0) |
216 | return pam_syslog_errno(handle, LOG_ERR, r, "Failed to parse JSON user record: %m"); | |
9ab0d3eb LP |
217 | |
218 | ur = user_record_new(); | |
219 | if (!ur) | |
220 | return pam_log_oom(handle); | |
221 | ||
bfc0cc1a | 222 | r = user_record_load(ur, v, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE); |
7e7b53b4 ZJS |
223 | if (r < 0) |
224 | return pam_syslog_errno(handle, LOG_ERR, r, "Failed to load user record: %m"); | |
9ab0d3eb LP |
225 | |
226 | /* Safety check if cached record actually matches what we are looking for */ | |
8aacf0fe | 227 | if (!user_record_matches_user_name(ur, username)) |
f48e9376 ZJS |
228 | return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, |
229 | "Acquired user record does not match user name."); | |
dbe7fff4 LP |
230 | } else { |
231 | _cleanup_free_ char *formatted = NULL; | |
232 | ||
233 | /* Request the record ourselves */ | |
74192916 | 234 | r = userdb_by_name(username, /* match= */ NULL, /* flags= */ 0, &ur); |
dbe7fff4 | 235 | if (r < 0) { |
7e7b53b4 | 236 | pam_syslog_errno(handle, LOG_ERR, r, "Failed to get user record: %m"); |
dbe7fff4 LP |
237 | return PAM_USER_UNKNOWN; |
238 | } | |
239 | ||
91d11d53 LP |
240 | if (!uid_is_valid(ur->uid)) { |
241 | pam_syslog_errno(handle, LOG_ERR, r, "User record of user '%s' has no UID, refusing: %m", username); | |
242 | return PAM_USER_UNKNOWN; | |
243 | } | |
244 | ||
309a747f | 245 | r = sd_json_variant_format(ur->json, 0, &formatted); |
7e7b53b4 ZJS |
246 | if (r < 0) |
247 | return pam_syslog_errno(handle, LOG_ERR, r, "Failed to format user JSON: %m"); | |
dbe7fff4 LP |
248 | |
249 | /* And cache it for everyone else */ | |
250 | r = pam_set_data(handle, field, formatted, pam_cleanup_free); | |
f48e9376 ZJS |
251 | if (r != PAM_SUCCESS) |
252 | return pam_syslog_pam_error(handle, LOG_ERR, r, | |
253 | "Failed to set PAM user record data '%s': @PAMERR@", field); | |
dbe7fff4 | 254 | TAKE_PTR(formatted); |
9ab0d3eb LP |
255 | } |
256 | ||
f48e9376 ZJS |
257 | if (!uid_is_valid(ur->uid)) |
258 | return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, | |
259 | "Acquired user record does not have a UID."); | |
8c6db833 | 260 | |
9ab0d3eb LP |
261 | if (ret_record) |
262 | *ret_record = TAKE_PTR(ur); | |
8c6db833 LP |
263 | |
264 | return PAM_SUCCESS; | |
265 | } | |
266 | ||
ecd5f1a9 LP |
267 | static bool display_is_local(const char *display) { |
268 | assert(display); | |
269 | ||
270 | return | |
271 | display[0] == ':' && | |
ff25d338 | 272 | ascii_isdigit(display[1]); |
ecd5f1a9 LP |
273 | } |
274 | ||
ddf127cd TM |
275 | static int socket_from_display(const char *display) { |
276 | _cleanup_free_ char *f = NULL; | |
f7b8b5c4 | 277 | size_t k; |
ddf127cd TM |
278 | char *c; |
279 | union sockaddr_union sa; | |
280 | socklen_t sa_len; | |
254d1313 | 281 | _cleanup_close_ int fd = -EBADF; |
ddf127cd | 282 | int r; |
f7b8b5c4 LP |
283 | |
284 | assert(display); | |
f7b8b5c4 LP |
285 | |
286 | if (!display_is_local(display)) | |
287 | return -EINVAL; | |
288 | ||
289 | k = strspn(display+1, "0123456789"); | |
290 | ||
ddf127cd TM |
291 | /* Try abstract socket first. */ |
292 | f = new(char, STRLEN("@/tmp/.X11-unix/X") + k + 1); | |
f7b8b5c4 LP |
293 | if (!f) |
294 | return -ENOMEM; | |
295 | ||
ddf127cd | 296 | c = stpcpy(f, "@/tmp/.X11-unix/X"); |
f7b8b5c4 LP |
297 | memcpy(c, display+1, k); |
298 | c[k] = 0; | |
299 | ||
ddf127cd TM |
300 | r = sockaddr_un_set_path(&sa.un, f); |
301 | if (r < 0) | |
302 | return r; | |
303 | sa_len = r; | |
f7b8b5c4 | 304 | |
ddf127cd TM |
305 | fd = RET_NERRNO(socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0)); |
306 | if (fd < 0) | |
307 | return fd; | |
308 | ||
309 | r = RET_NERRNO(connect(fd, &sa.sa, sa_len)); | |
310 | if (r >= 0) | |
311 | return TAKE_FD(fd); | |
312 | if (r != -ECONNREFUSED) | |
313 | return r; | |
314 | ||
315 | /* Try also non-abstract socket. */ | |
316 | r = sockaddr_un_set_path(&sa.un, f + 1); | |
317 | if (r < 0) | |
318 | return r; | |
319 | sa_len = r; | |
320 | ||
321 | r = RET_NERRNO(connect(fd, &sa.sa, sa_len)); | |
322 | if (r >= 0) | |
323 | return TAKE_FD(fd); | |
324 | return r; | |
f7b8b5c4 LP |
325 | } |
326 | ||
4d6d6518 | 327 | static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) { |
ddf127cd | 328 | _cleanup_free_ char *sys_path = NULL, *tty = NULL; |
254d1313 | 329 | _cleanup_close_ int fd = -EBADF; |
4d6d6518 | 330 | struct ucred ucred; |
f36a9d59 | 331 | int v, r; |
e21d9060 | 332 | dev_t display_ctty; |
4d6d6518 LP |
333 | |
334 | assert(display); | |
4d6d6518 LP |
335 | assert(vtnr); |
336 | ||
337 | /* We deduce the X11 socket from the display name, then use | |
338 | * SO_PEERCRED to determine the X11 server process, ask for | |
339 | * the controlling tty of that and if it's a VC then we know | |
340 | * the seat and the virtual terminal. Sounds ugly, is only | |
341 | * semi-ugly. */ | |
342 | ||
ddf127cd | 343 | fd = socket_from_display(display); |
b92bea5d | 344 | if (fd < 0) |
ddf127cd | 345 | return fd; |
4d6d6518 | 346 | |
eff05270 | 347 | r = getpeercred(fd, &ucred); |
4d6d6518 | 348 | if (r < 0) |
eff05270 | 349 | return r; |
4d6d6518 | 350 | |
e21d9060 TM |
351 | r = get_ctty_devnr(ucred.pid, &display_ctty); |
352 | if (r < 0) | |
353 | return r; | |
354 | ||
3a4e4ffa | 355 | if (asprintf(&sys_path, "/sys/dev/char/" DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(display_ctty)) < 0) |
e21d9060 TM |
356 | return -ENOMEM; |
357 | r = readlink_value(sys_path, &tty); | |
4d6d6518 LP |
358 | if (r < 0) |
359 | return r; | |
360 | ||
361 | v = vtnr_from_tty(tty); | |
4d6d6518 LP |
362 | if (v < 0) |
363 | return v; | |
d25b55b9 | 364 | assert(v > 0); |
4d6d6518 | 365 | |
fc7985ed LP |
366 | if (seat) |
367 | *seat = "seat0"; | |
4d6d6518 LP |
368 | *vtnr = (uint32_t) v; |
369 | ||
370 | return 0; | |
371 | } | |
372 | ||
00efd498 ZJS |
373 | static int export_legacy_dbus_address( |
374 | pam_handle_t *handle, | |
00efd498 ZJS |
375 | const char *runtime) { |
376 | ||
c9802426 LP |
377 | int r; |
378 | ||
379 | assert(handle); | |
00efd498 | 380 | |
15ee6c20 | 381 | /* We need to export $DBUS_SESSION_BUS_ADDRESS because various applications will not connect |
c9802426 LP |
382 | * correctly to the bus without it. This setting matches what dbus.socket does for the user session |
383 | * using 'systemctl --user set-environment'. We want to have the same configuration in processes | |
384 | * started from the PAM session. | |
15ee6c20 ZJS |
385 | * |
386 | * The setting of the address is guarded by the access() check because it is also possible to compile | |
387 | * dbus without --enable-user-session, in which case this socket is not used, and | |
388 | * $DBUS_SESSION_BUS_ADDRESS should not be set. An alternative approach would to not do the access() | |
389 | * check here, and let applications try on their own, by using "unix:path=%s/bus;autolaunch:". But we | |
390 | * expect the socket to be present by the time we do this check, so we can just as well check once | |
391 | * here. */ | |
392 | ||
c9802426 | 393 | if (!runtime) |
15ee6c20 ZJS |
394 | return PAM_SUCCESS; |
395 | ||
c9802426 LP |
396 | const char *s = strjoina(runtime, "/bus"); |
397 | if (access(s, F_OK) < 0) { | |
398 | if (errno != ENOENT) | |
399 | pam_syslog_errno(handle, LOG_WARNING, errno, "Failed to check if %s/bus exists, ignoring: %m", runtime); | |
400 | ||
401 | return PAM_SUCCESS; | |
402 | } | |
403 | ||
404 | _cleanup_free_ char *t = NULL; | |
15ee6c20 | 405 | if (asprintf(&t, DEFAULT_USER_BUS_ADDRESS_FMT, runtime) < 0) |
d750dde2 | 406 | return pam_log_oom(handle); |
00efd498 | 407 | |
c9802426 | 408 | r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", t, /* readonly= */ false); |
f48e9376 ZJS |
409 | if (r != PAM_SUCCESS) |
410 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set bus variable: @PAMERR@"); | |
00efd498 ZJS |
411 | |
412 | return PAM_SUCCESS; | |
00efd498 ZJS |
413 | } |
414 | ||
22f93314 | 415 | static int append_session_memory_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) { |
22f93314 JS |
416 | int r; |
417 | ||
7f471bd3 LP |
418 | assert(handle); |
419 | assert(m); | |
22f93314 | 420 | |
7f471bd3 LP |
421 | if (isempty(limit)) |
422 | return 0; | |
d750dde2 | 423 | |
7f471bd3 LP |
424 | if (streq(limit, "infinity")) |
425 | return sd_bus_message_append(m, "(sv)", "MemoryMax", "t", UINT64_MAX); | |
d750dde2 | 426 | |
fe845b5e | 427 | r = parse_permyriad(limit); |
7f471bd3 LP |
428 | if (r >= 0) |
429 | return sd_bus_message_append(m, "(sv)", "MemoryMaxScale", "u", UINT32_SCALE_FROM_PERMYRIAD(r)); | |
d750dde2 | 430 | |
7f471bd3 | 431 | uint64_t val; |
d750dde2 | 432 | r = parse_size(limit, 1024, &val); |
7f471bd3 LP |
433 | if (r < 0) { |
434 | pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.memory_max, ignoring: %s", limit); | |
7bfbf6cc | 435 | return PAM_SUCCESS; |
22f93314 JS |
436 | } |
437 | ||
7f471bd3 | 438 | return sd_bus_message_append(m, "(sv)", "MemoryMax", "t", val); |
22f93314 JS |
439 | } |
440 | ||
adc09af2 | 441 | static int append_session_runtime_max_sec(pam_handle_t *handle, sd_bus_message *m, const char *limit) { |
adc09af2 PW |
442 | int r; |
443 | ||
7f471bd3 LP |
444 | assert(handle); |
445 | assert(m); | |
446 | ||
adc09af2 PW |
447 | /* No need to parse "infinity" here, it will be set by default later in scope_init() */ |
448 | if (isempty(limit) || streq(limit, "infinity")) | |
7f471bd3 | 449 | return 0; |
adc09af2 | 450 | |
7f471bd3 | 451 | usec_t val; |
adc09af2 | 452 | r = parse_sec(limit, &val); |
7f471bd3 | 453 | if (r < 0) { |
adc09af2 | 454 | pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.runtime_max_sec: %s, ignoring.", limit); |
7f471bd3 LP |
455 | return 0; |
456 | } | |
adc09af2 | 457 | |
7f471bd3 | 458 | return sd_bus_message_append(m, "(sv)", "RuntimeMaxUSec", "t", (uint64_t) val); |
adc09af2 PW |
459 | } |
460 | ||
5fdfbbd5 | 461 | static int append_session_tasks_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) { |
22f93314 JS |
462 | int r; |
463 | ||
7f471bd3 LP |
464 | assert(handle); |
465 | assert(m); | |
466 | ||
22f93314 JS |
467 | /* No need to parse "infinity" here, it will be set unconditionally later in manager_start_scope() */ |
468 | if (isempty(limit) || streq(limit, "infinity")) | |
7f471bd3 | 469 | return 0; |
22f93314 | 470 | |
7f471bd3 | 471 | uint64_t val; |
22f93314 | 472 | r = safe_atou64(limit, &val); |
7f471bd3 | 473 | if (r < 0) { |
d750dde2 | 474 | pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.tasks_max, ignoring: %s", limit); |
7f471bd3 LP |
475 | return 0; |
476 | } | |
22f93314 | 477 | |
7f471bd3 | 478 | return sd_bus_message_append(m, "(sv)", "TasksMax", "t", val); |
22f93314 JS |
479 | } |
480 | ||
e7906abe | 481 | static int append_session_cpu_weight(pam_handle_t *handle, sd_bus_message *m, const char *limit) { |
22f93314 JS |
482 | int r; |
483 | ||
7f471bd3 LP |
484 | assert(handle); |
485 | assert(m); | |
486 | ||
36a4dbae | 487 | if (isempty(limit)) |
7f471bd3 | 488 | return 0; |
36a4dbae | 489 | |
7f471bd3 | 490 | uint64_t val; |
e7906abe | 491 | r = cg_cpu_weight_parse(limit, &val); |
7f471bd3 | 492 | if (r < 0) { |
e7906abe | 493 | pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.cpu_weight, ignoring: %s", limit); |
7f471bd3 | 494 | return 0; |
e7906abe LP |
495 | } |
496 | ||
7f471bd3 | 497 | return sd_bus_message_append(m, "(sv)", "CPUWeight", "t", val); |
e7906abe LP |
498 | } |
499 | ||
500 | static int append_session_io_weight(pam_handle_t *handle, sd_bus_message *m, const char *limit) { | |
e7906abe LP |
501 | int r; |
502 | ||
7f471bd3 LP |
503 | assert(handle); |
504 | assert(m); | |
505 | ||
e7906abe | 506 | if (isempty(limit)) |
7f471bd3 | 507 | return 0; |
e7906abe | 508 | |
7f471bd3 | 509 | uint64_t val; |
e7906abe | 510 | r = cg_weight_parse(limit, &val); |
7f471bd3 | 511 | if (r < 0) { |
d750dde2 | 512 | pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.io_weight, ignoring: %s", limit); |
7f471bd3 | 513 | return 0; |
e7906abe | 514 | } |
22f93314 | 515 | |
7f471bd3 | 516 | return sd_bus_message_append(m, "(sv)", "IOWeight", "t", val); |
22f93314 JS |
517 | } |
518 | ||
0ecc1c9d LP |
519 | static const char* getenv_harder(pam_handle_t *handle, const char *key, const char *fallback) { |
520 | const char *v; | |
521 | ||
522 | assert(handle); | |
523 | assert(key); | |
524 | ||
5238e957 | 525 | /* Looks for an environment variable, preferably in the environment block associated with the |
83d4ab55 LP |
526 | * specified PAM handle, falling back to the process' block instead. Why check both? Because we want |
527 | * to permit configuration of session properties from unit files that invoke PAM services, so that | |
528 | * PAM services don't have to be reworked to set systemd-specific properties, but these properties | |
529 | * can still be set from the unit file Environment= block. */ | |
0ecc1c9d LP |
530 | |
531 | v = pam_getenv(handle, key); | |
532 | if (!isempty(v)) | |
533 | return v; | |
534 | ||
83d4ab55 LP |
535 | /* We use secure_getenv() here, since we might get loaded into su/sudo, which are SUID. Ideally |
536 | * they'd clean up the environment before invoking foreign code (such as PAM modules), but alas they | |
537 | * currently don't (to be precise, they clean up the environment they pass to their children, but | |
538 | * not their own environ[]). */ | |
539 | v = secure_getenv(key); | |
0ecc1c9d LP |
540 | if (!isempty(v)) |
541 | return v; | |
542 | ||
543 | return fallback; | |
544 | } | |
545 | ||
249067d1 LP |
546 | static bool getenv_harder_bool(pam_handle_t *handle, const char *key, bool fallback) { |
547 | const char *v; | |
548 | int r; | |
549 | ||
550 | assert(handle); | |
551 | assert(key); | |
552 | ||
553 | v = getenv_harder(handle, key, NULL); | |
554 | if (isempty(v)) | |
555 | return fallback; | |
556 | ||
557 | r = parse_boolean(v); | |
558 | if (r < 0) { | |
6082ccf7 | 559 | pam_syslog(handle, LOG_WARNING, "Failed to parse environment variable value '%s' of '%s', falling back to using '%s'.", v, key, true_false(fallback)); |
249067d1 LP |
560 | return fallback; |
561 | } | |
562 | ||
563 | return r; | |
564 | } | |
565 | ||
6082ccf7 LP |
566 | static uint32_t getenv_harder_uint32(pam_handle_t *handle, const char *key, uint32_t fallback) { |
567 | int r; | |
568 | ||
569 | assert(handle); | |
570 | assert(key); | |
571 | ||
572 | const char *v = getenv_harder(handle, key, NULL); | |
573 | if (isempty(v)) | |
574 | return fallback; | |
575 | ||
576 | uint32_t u; | |
577 | r = safe_atou32(v, &u); | |
578 | if (r < 0) { | |
579 | pam_syslog(handle, LOG_WARNING, "Failed to parse environment variable value '%s' of '%s' as unsigned integer, falling back to using %" PRIu32 ".", v, key, fallback); | |
580 | return fallback; | |
581 | } | |
582 | ||
583 | return u; | |
584 | } | |
585 | ||
d6baaa69 LP |
586 | static int update_environment(pam_handle_t *handle, const char *key, const char *value) { |
587 | int r; | |
588 | ||
589 | assert(handle); | |
590 | assert(key); | |
591 | ||
e35b78f1 LP |
592 | /* Updates the environment, and removes environment variables if value is NULL or empty. Also, log |
593 | * about errors. */ | |
594 | ||
595 | if (isempty(value)) { | |
596 | /* Unset the variable if set. Note that pam_putenv() would log nastily behind our back if we | |
597 | * call it without the variable actually being set. Hence we check explicitly if it's set | |
598 | * before. */ | |
599 | ||
600 | if (!pam_getenv(handle, key)) | |
601 | return PAM_SUCCESS; | |
602 | ||
603 | r = pam_putenv(handle, key); | |
604 | if (!IN_SET(r, PAM_SUCCESS, PAM_BAD_ITEM)) | |
605 | return pam_syslog_pam_error(handle, LOG_WARNING, r, | |
606 | "Failed to unset %s environment variable: @PAMERR@", key); | |
d6baaa69 | 607 | |
d6baaa69 | 608 | return PAM_SUCCESS; |
e35b78f1 | 609 | } |
d6baaa69 | 610 | |
e35b78f1 | 611 | r = pam_misc_setenv(handle, key, value, /* readonly= */ false); |
d6baaa69 | 612 | if (r != PAM_SUCCESS) |
f48e9376 ZJS |
613 | return pam_syslog_pam_error(handle, LOG_ERR, r, |
614 | "Failed to set environment variable %s: @PAMERR@", key); | |
d6baaa69 | 615 | |
f48e9376 | 616 | return PAM_SUCCESS; |
d6baaa69 LP |
617 | } |
618 | ||
229d4a98 LP |
619 | static int propagate_credential_to_environment(pam_handle_t *handle, const char *credential, const char *varname) { |
620 | int r; | |
621 | ||
622 | assert(handle); | |
623 | assert(credential); | |
624 | assert(varname); | |
625 | ||
626 | _cleanup_free_ char *value = NULL; | |
627 | ||
628 | /* Read a service credential, and propagate it into an environment variable */ | |
629 | ||
630 | r = read_credential(credential, (void**) &value, /* ret_size= */ NULL); | |
631 | if (r < 0) { | |
632 | log_debug_errno(r, "Failed to read credential '%s', ignoring: %m", credential); | |
633 | return PAM_SUCCESS; | |
634 | } | |
635 | ||
636 | r = pam_misc_setenv(handle, varname, value, 0); | |
637 | if (r != PAM_SUCCESS) | |
638 | return pam_syslog_pam_error(handle, LOG_ERR, r, | |
639 | "Failed to set environment variable %s: @PAMERR@", varname); | |
640 | ||
641 | return PAM_SUCCESS; | |
642 | } | |
643 | ||
b9217112 LP |
644 | static bool validate_runtime_directory(pam_handle_t *handle, const char *path, uid_t uid) { |
645 | struct stat st; | |
646 | ||
dca81e28 | 647 | assert(handle); |
b9217112 LP |
648 | assert(path); |
649 | ||
e0d70f76 LP |
650 | /* Some extra paranoia: let's not set $XDG_RUNTIME_DIR if the directory we'd set it to isn't actually |
651 | * set up properly for us. This is supposed to provide a careful safety net for supporting su/sudo | |
652 | * type transitions: in that case the UID changes, but the session and thus the user owning it | |
15e6a6e8 | 653 | * doesn't change. Since the $XDG_RUNTIME_DIR lifecycle is bound to the session's user being logged |
e0d70f76 LP |
654 | * in at least once we should be particularly careful when setting the environment variable, since |
655 | * otherwise we might end up setting $XDG_RUNTIME_DIR to some directory owned by the wrong user. */ | |
b9217112 | 656 | |
6d06dfad LP |
657 | if (!path_is_absolute(path)) { |
658 | pam_syslog(handle, LOG_ERR, "Provided runtime directory '%s' is not absolute.", path); | |
659 | goto fail; | |
660 | } | |
661 | ||
b9217112 | 662 | if (lstat(path, &st) < 0) { |
7e7b53b4 | 663 | pam_syslog_errno(handle, LOG_ERR, errno, "Failed to stat() runtime directory '%s': %m", path); |
b9217112 LP |
664 | goto fail; |
665 | } | |
666 | ||
667 | if (!S_ISDIR(st.st_mode)) { | |
668 | pam_syslog(handle, LOG_ERR, "Runtime directory '%s' is not actually a directory.", path); | |
669 | goto fail; | |
670 | } | |
671 | ||
672 | if (st.st_uid != uid) { | |
673 | pam_syslog(handle, LOG_ERR, "Runtime directory '%s' is not owned by UID " UID_FMT ", as it should.", path, uid); | |
674 | goto fail; | |
675 | } | |
676 | ||
677 | return true; | |
678 | ||
679 | fail: | |
680 | pam_syslog(handle, LOG_WARNING, "Not setting $XDG_RUNTIME_DIR, as the directory is not in order."); | |
681 | return false; | |
682 | } | |
683 | ||
f9c1f4e1 LP |
684 | static int pam_putenv_and_log(pam_handle_t *handle, const char *e, bool debug) { |
685 | int r; | |
686 | ||
687 | assert(handle); | |
688 | assert(e); | |
689 | ||
690 | r = pam_putenv(handle, e); | |
f48e9376 ZJS |
691 | if (r != PAM_SUCCESS) |
692 | return pam_syslog_pam_error(handle, LOG_ERR, r, | |
693 | "Failed to set PAM environment variable %s: @PAMERR@", e); | |
f9c1f4e1 | 694 | |
27ccba26 | 695 | pam_debug_syslog(handle, debug, "PAM environment variable %s set based on user record.", e); |
f9c1f4e1 LP |
696 | |
697 | return PAM_SUCCESS; | |
698 | } | |
699 | ||
bf1b9ae4 LP |
700 | static int apply_user_record_settings( |
701 | pam_handle_t *handle, | |
702 | UserRecord *ur, | |
703 | bool debug, | |
704 | uint64_t default_capability_bounding_set, | |
705 | uint64_t default_capability_ambient_set) { | |
49e55abb | 706 | _cleanup_strv_free_ char **langs = NULL; |
f9c1f4e1 LP |
707 | int r; |
708 | ||
709 | assert(handle); | |
710 | assert(ur); | |
711 | ||
712 | if (ur->umask != MODE_INVALID) { | |
713 | umask(ur->umask); | |
27ccba26 | 714 | pam_debug_syslog(handle, debug, "Set user umask to %04o based on user record.", ur->umask); |
f9c1f4e1 LP |
715 | } |
716 | ||
717 | STRV_FOREACH(i, ur->environment) { | |
f9c1f4e1 LP |
718 | r = pam_putenv_and_log(handle, *i, debug); |
719 | if (r != PAM_SUCCESS) | |
720 | return r; | |
721 | } | |
722 | ||
723 | if (ur->email_address) { | |
592ca6f0 | 724 | _cleanup_free_ char *joined = NULL; |
f9c1f4e1 | 725 | |
592ca6f0 AV |
726 | joined = strjoin("EMAIL=", ur->email_address); |
727 | if (!joined) | |
728 | return pam_log_oom(handle); | |
f9c1f4e1 | 729 | |
592ca6f0 AV |
730 | r = pam_putenv_and_log(handle, joined, debug); |
731 | if (r != PAM_SUCCESS) | |
732 | return r; | |
f9c1f4e1 LP |
733 | } |
734 | ||
735 | if (ur->time_zone) { | |
592ca6f0 | 736 | if (!timezone_is_valid(ur->time_zone, LOG_DEBUG)) |
27ccba26 ZJS |
737 | pam_debug_syslog(handle, debug, |
738 | "Time zone specified in user record is not valid locally, not setting $TZ."); | |
739 | else { | |
f9c1f4e1 LP |
740 | _cleanup_free_ char *joined = NULL; |
741 | ||
742 | joined = strjoin("TZ=:", ur->time_zone); | |
743 | if (!joined) | |
744 | return pam_log_oom(handle); | |
745 | ||
746 | r = pam_putenv_and_log(handle, joined, debug); | |
747 | if (r != PAM_SUCCESS) | |
748 | return r; | |
749 | } | |
750 | } | |
751 | ||
49e55abb AV |
752 | r = user_record_languages(ur, &langs); |
753 | if (r < 0) | |
754 | pam_syslog_errno(handle, LOG_ERR, r, | |
755 | "Failed to acquire user's language preferences, ignoring: %m"); | |
756 | else if (strv_isempty(langs)) | |
757 | ; /* User has no preference set so we do nothing */ | |
758 | else if (locale_is_installed(langs[0]) <= 0) | |
759 | pam_debug_syslog(handle, debug, | |
760 | "Preferred languages specified in user record are not installed locally, not setting $LANG or $LANGUAGE."); | |
761 | else { | |
762 | _cleanup_free_ char *lang = NULL; | |
763 | ||
764 | lang = strjoin("LANG=", langs[0]); | |
765 | if (!lang) | |
766 | return pam_log_oom(handle); | |
f9c1f4e1 | 767 | |
49e55abb AV |
768 | r = pam_putenv_and_log(handle, lang, debug); |
769 | if (r != PAM_SUCCESS) | |
770 | return r; | |
771 | ||
772 | if (strv_length(langs) > 1) { | |
773 | _cleanup_free_ char *joined = NULL, *language = NULL; | |
774 | ||
775 | joined = strv_join(langs, ":"); | |
f9c1f4e1 LP |
776 | if (!joined) |
777 | return pam_log_oom(handle); | |
778 | ||
49e55abb AV |
779 | language = strjoin("LANGUAGE=", joined); |
780 | if (!language) | |
781 | return pam_log_oom(handle); | |
782 | ||
783 | r = pam_putenv_and_log(handle, language, debug); | |
f9c1f4e1 LP |
784 | if (r != PAM_SUCCESS) |
785 | return r; | |
786 | } | |
787 | } | |
788 | ||
789 | if (nice_is_valid(ur->nice_level)) { | |
790 | if (nice(ur->nice_level) < 0) | |
29f2057a | 791 | pam_syslog_errno(handle, LOG_WARNING, errno, |
7e7b53b4 | 792 | "Failed to set nice level to %i, ignoring: %m", ur->nice_level); |
27ccba26 ZJS |
793 | else |
794 | pam_debug_syslog(handle, debug, | |
795 | "Nice level set to %i, based on user record.", ur->nice_level); | |
f9c1f4e1 LP |
796 | } |
797 | ||
798 | for (int rl = 0; rl < _RLIMIT_MAX; rl++) { | |
f9c1f4e1 LP |
799 | if (!ur->rlimits[rl]) |
800 | continue; | |
801 | ||
802 | r = setrlimit_closest(rl, ur->rlimits[rl]); | |
803 | if (r < 0) | |
7e7b53b4 ZJS |
804 | pam_syslog_errno(handle, LOG_ERR, r, |
805 | "Failed to set resource limit %s, ignoring: %m", rlimit_to_string(rl)); | |
27ccba26 ZJS |
806 | else |
807 | pam_debug_syslog(handle, debug, | |
808 | "Resource limit %s set, based on user record.", rlimit_to_string(rl)); | |
f9c1f4e1 LP |
809 | } |
810 | ||
bf1b9ae4 LP |
811 | uint64_t a, b; |
812 | a = user_record_capability_ambient_set(ur); | |
813 | if (a == UINT64_MAX) | |
814 | a = default_capability_ambient_set; | |
815 | ||
816 | b = user_record_capability_bounding_set(ur); | |
817 | if (b == UINT64_MAX) | |
818 | b = default_capability_bounding_set; | |
819 | ||
820 | if (a != UINT64_MAX && a != 0) { | |
821 | a &= b; | |
822 | ||
823 | r = capability_ambient_set_apply(a, /* also_inherit= */ true); | |
824 | if (r < 0) | |
825 | pam_syslog_errno(handle, LOG_ERR, r, | |
826 | "Failed to set ambient capabilities, ignoring: %m"); | |
827 | } | |
828 | ||
829 | if (b != UINT64_MAX && !cap_test_all(b)) { | |
830 | r = capability_bounding_set_drop(b, /* right_now= */ false); | |
831 | if (r < 0) | |
832 | pam_syslog_errno(handle, LOG_ERR, r, | |
833 | "Failed to set bounding capabilities, ignoring: %m"); | |
834 | } | |
835 | ||
f9c1f4e1 LP |
836 | return PAM_SUCCESS; |
837 | } | |
838 | ||
bf1b9ae4 LP |
839 | static uint64_t pick_default_capability_ambient_set( |
840 | UserRecord *ur, | |
841 | const char *service, | |
842 | const char *seat) { | |
843 | ||
844 | /* If not configured otherwise, let's enable CAP_WAKE_ALARM for regular users when logging in on a | |
845 | * seat (i.e. when they are present physically on the device), or when invoked for the systemd --user | |
846 | * instances. This allows desktops to install CAP_WAKE_ALARM to implement alarm clock apps without | |
847 | * much fuss. */ | |
848 | ||
849 | return ur && | |
850 | user_record_disposition(ur) == USER_REGULAR && | |
851 | (streq_ptr(service, "systemd-user") || !isempty(seat)) ? (UINT64_C(1) << CAP_WAKE_ALARM) : UINT64_MAX; | |
852 | } | |
853 | ||
76f2191d | 854 | typedef struct SessionContext { |
76f2191d MS |
855 | const char *service; |
856 | const char *type; | |
857 | const char *class; | |
858 | const char *desktop; | |
859 | const char *seat; | |
32580792 | 860 | uint32_t vtnr; |
76f2191d MS |
861 | const char *tty; |
862 | const char *display; | |
32580792 | 863 | bool remote; |
76f2191d MS |
864 | const char *remote_user; |
865 | const char *remote_host; | |
866 | const char *memory_max; | |
867 | const char *tasks_max; | |
868 | const char *cpu_weight; | |
869 | const char *io_weight; | |
870 | const char *runtime_max_sec; | |
c747c041 | 871 | const char *area; |
32580792 | 872 | bool incomplete; |
76f2191d MS |
873 | } SessionContext; |
874 | ||
79f36b64 MY |
875 | static int create_session_message( |
876 | sd_bus *bus, | |
877 | pam_handle_t *handle, | |
5e782e4d | 878 | UserRecord *ur, |
79f36b64 MY |
879 | const SessionContext *context, |
880 | bool avoid_pidfd, | |
881 | sd_bus_message **ret) { | |
882 | ||
76f2191d | 883 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; |
79f36b64 MY |
884 | _cleanup_close_ int pidfd = -EBADF; |
885 | int r; | |
76f2191d MS |
886 | |
887 | assert(bus); | |
888 | assert(handle); | |
5e782e4d | 889 | assert(ur); |
76f2191d | 890 | assert(context); |
79f36b64 | 891 | assert(ret); |
76f2191d MS |
892 | |
893 | if (!avoid_pidfd) { | |
894 | pidfd = pidfd_open(getpid_cached(), 0); | |
6e14c46b | 895 | if (pidfd < 0) |
76f2191d MS |
896 | return -errno; |
897 | } | |
898 | ||
899 | r = bus_message_new_method_call(bus, &m, bus_login_mgr, pidfd >= 0 ? "CreateSessionWithPIDFD" : "CreateSession"); | |
900 | if (r < 0) | |
901 | return r; | |
902 | ||
7f471bd3 LP |
903 | r = sd_bus_message_append( |
904 | m, | |
905 | pidfd >= 0 ? "uhsssssussbss" : "uusssssussbss", | |
906 | (uint32_t) ur->uid, | |
907 | pidfd >= 0 ? pidfd : 0, | |
908 | context->service, | |
909 | context->type, | |
910 | context->class, | |
911 | context->desktop, | |
912 | context->seat, | |
913 | context->vtnr, | |
914 | context->tty, | |
915 | context->display, | |
916 | context->remote, | |
917 | context->remote_user, | |
918 | context->remote_host); | |
76f2191d MS |
919 | if (r < 0) |
920 | return r; | |
921 | ||
922 | if (pidfd >= 0) { | |
923 | r = sd_bus_message_append(m, "t", UINT64_C(0)); | |
924 | if (r < 0) | |
925 | return r; | |
926 | } | |
927 | ||
928 | r = sd_bus_message_open_container(m, 'a', "(sv)"); | |
929 | if (r < 0) | |
930 | return r; | |
931 | ||
932 | r = append_session_memory_max(handle, m, context->memory_max); | |
7f471bd3 | 933 | if (r < 0) |
76f2191d MS |
934 | return r; |
935 | ||
936 | r = append_session_runtime_max_sec(handle, m, context->runtime_max_sec); | |
7f471bd3 | 937 | if (r < 0) |
76f2191d MS |
938 | return r; |
939 | ||
940 | r = append_session_tasks_max(handle, m, context->tasks_max); | |
7f471bd3 | 941 | if (r < 0) |
76f2191d MS |
942 | return r; |
943 | ||
944 | r = append_session_cpu_weight(handle, m, context->cpu_weight); | |
7f471bd3 | 945 | if (r < 0) |
76f2191d MS |
946 | return r; |
947 | ||
948 | r = append_session_io_weight(handle, m, context->io_weight); | |
7f471bd3 | 949 | if (r < 0) |
76f2191d MS |
950 | return r; |
951 | ||
952 | r = sd_bus_message_close_container(m); | |
953 | if (r < 0) | |
954 | return r; | |
955 | ||
956 | *ret = TAKE_PTR(m); | |
957 | return 0; | |
958 | } | |
959 | ||
32580792 | 960 | static void session_context_mangle( |
8c6db833 | 961 | pam_handle_t *handle, |
32580792 LP |
962 | SessionContext *c, |
963 | UserRecord *ur, | |
964 | bool debug) { | |
8c6db833 | 965 | |
ffcfcb6b | 966 | assert(handle); |
32580792 LP |
967 | assert(c); |
968 | assert(ur); | |
98a28fef | 969 | |
32580792 | 970 | if (streq_ptr(c->service, "systemd-user")) { |
4cb4e6cf LP |
971 | /* If we detect that we are running in the "systemd-user" PAM stack, then let's patch the class to |
972 | * 'manager' if not set, simply for robustness reasons. */ | |
32580792 LP |
973 | c->type = "unspecified"; |
974 | c->class = IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) ? | |
4cb4e6cf | 975 | "manager-early" : "manager"; |
32580792 | 976 | c->tty = NULL; |
4cb4e6cf | 977 | |
32580792 | 978 | } else if (c->tty && strchr(c->tty, ':')) { |
3a736949 LP |
979 | /* A tty with a colon is usually an X11 display, placed there to show up in utmp. We rearrange things |
980 | * and don't pretend that an X display was a tty. */ | |
32580792 LP |
981 | if (isempty(c->display)) |
982 | c->display = c->tty; | |
983 | c->tty = NULL; | |
3a736949 | 984 | |
32580792 | 985 | } else if (streq_ptr(c->tty, "cron")) { |
3a736949 LP |
986 | /* cron is setting PAM_TTY to "cron" for some reason (the commit carries no information why, but |
987 | * probably because it wants to set it to something as pam_time/pam_access/… require PAM_TTY to be set | |
988 | * (as they otherwise even try to update it!) — but cron doesn't actually allocate a TTY for its forked | |
989 | * off processes.) */ | |
32580792 LP |
990 | c->type = "unspecified"; |
991 | c->class = "background"; | |
992 | c->tty = NULL; | |
3a736949 | 993 | |
32580792 | 994 | } else if (streq_ptr(c->tty, "ssh")) { |
3a736949 LP |
995 | /* ssh has been setting PAM_TTY to "ssh" (for the same reason as cron does this, see above. For further |
996 | * details look for "PAM_TTY_KLUDGE" in the openssh sources). */ | |
32580792 LP |
997 | c->type = "tty"; |
998 | c->class = "user"; | |
999 | c->tty = NULL; /* This one is particularly sad, as this means that ssh sessions — even though | |
1000 | * usually associated with a pty — won't be tracked by their tty in | |
1001 | * logind. This is because ssh does the PAM session registration early for new | |
1002 | * connections, and registers a pty only much later (this is because it doesn't | |
1003 | * know yet if it needs one at all, as whether to register a pty or not is | |
1004 | * negotiated much later in the protocol). */ | |
1005 | ||
1006 | } else if (c->tty) | |
c9ed61e7 | 1007 | /* Chop off leading /dev prefix that some clients specify, but others do not. */ |
32580792 | 1008 | c->tty = skip_dev_prefix(c->tty); |
ee8545b0 | 1009 | |
32580792 LP |
1010 | if (!isempty(c->display) && !c->vtnr) { |
1011 | if (isempty(c->seat)) | |
1012 | (void) get_seat_from_display(c->display, &c->seat, &c->vtnr); | |
1013 | else if (streq(c->seat, "seat0")) | |
1014 | (void) get_seat_from_display(c->display, /* seat= */ NULL, &c->vtnr); | |
fc7985ed | 1015 | } |
4d6d6518 | 1016 | |
32580792 LP |
1017 | if (c->seat && !streq(c->seat, "seat0") && c->vtnr != 0) { |
1018 | pam_debug_syslog(handle, debug, "Ignoring vtnr %"PRIu32" for %s which is not seat0", c->vtnr, c->seat); | |
1019 | c->vtnr = 0; | |
d7353ef6 MM |
1020 | } |
1021 | ||
cf8f6cd0 | 1022 | if (isempty(c->type)) { |
32580792 LP |
1023 | c->type = !isempty(c->display) ? "x11" : |
1024 | !isempty(c->tty) ? "tty" : "unspecified"; | |
cf8f6cd0 LP |
1025 | pam_debug_syslog(handle, debug, "Automatically chose session type '%s'.", c->type); |
1026 | } | |
1027 | ||
52d5043b LP |
1028 | if (!c->area) |
1029 | c->area = ur->default_area; | |
1030 | ||
1031 | if (!isempty(c->area) && !filename_is_valid(c->area)) { | |
1032 | pam_syslog(handle, LOG_WARNING, "Specified area '%s' is not a valid filename, ignoring area request.", c->area); | |
1033 | c->area = NULL; | |
1034 | } | |
1035 | ||
cf8f6cd0 LP |
1036 | if (isempty(c->class)) { |
1037 | c->class = streq(c->type, "unspecified") ? "background" : "user"; | |
1038 | ||
1039 | /* For non-regular users tweak the type a bit: | |
1040 | * | |
1041 | * - Allow root tty logins *before* systemd-user-sessions.service is run, to allow early boot | |
1042 | * logins to debug things. | |
1043 | * | |
1044 | * - Non-graphical sessions shall be invoked without service manager. | |
1045 | * | |
1046 | * (Note that this somewhat replicates the class mangling logic on systemd-logind.service's | |
1047 | * server side to some degree, in case clients allocate a session and don't specify a | |
52d5043b LP |
1048 | * class. This is somewhat redundant, but we need the class set up properly below.) |
1049 | * | |
1050 | * For regular users also tweak the type a bit: if an area is specified at login time, switch | |
1051 | * to light mode too. (Mostly because at the moment we do no support a per-area service | |
1052 | * manager. Once we do, we should change this.). | |
1053 | */ | |
1054 | ||
1055 | switch (user_record_disposition(ur)) { | |
cf8f6cd0 | 1056 | |
52d5043b LP |
1057 | case USER_INTRINSIC: |
1058 | case USER_SYSTEM: | |
1059 | case USER_DYNAMIC: | |
cf8f6cd0 LP |
1060 | if (streq(c->class, "user")) |
1061 | c->class = user_record_is_root(ur) ? "user-early" : | |
1062 | (STR_IN_SET(c->type, "x11", "wayland", "mir") ? "user" : "user-light"); | |
1063 | else if (streq(c->class, "background")) | |
1064 | c->class = "background-light"; | |
52d5043b LP |
1065 | break; |
1066 | ||
1067 | case USER_REGULAR: | |
1068 | if (!isempty(c->area)) { | |
1069 | if (streq(c->class, "user")) | |
1070 | c->class = "user-light"; | |
1071 | else if (streq(c->class, "background")) | |
1072 | c->class = "background-light"; | |
1073 | } | |
1074 | ||
1075 | break; | |
1076 | ||
1077 | default: | |
5c9feb2d | 1078 | ; |
cf8f6cd0 | 1079 | } |
98a28fef | 1080 | |
cf8f6cd0 LP |
1081 | pam_debug_syslog(handle, debug, "Automatically chose session class '%s'.", c->class); |
1082 | } | |
55efac6c | 1083 | |
32580792 LP |
1084 | if (c->incomplete) { |
1085 | if (streq(c->class, "user")) | |
1086 | c->class = "user-incomplete"; | |
249067d1 | 1087 | else |
52d5043b | 1088 | pam_syslog(handle, LOG_WARNING, "PAM session of class '%s' is incomplete, which is not supported, ignoring.", c->class); |
249067d1 LP |
1089 | } |
1090 | ||
32580792 LP |
1091 | c->remote = !isempty(c->remote_host) && !is_localhost(c->remote_host); |
1092 | } | |
98a28fef | 1093 | |
4582f8d7 LP |
1094 | static bool can_use_varlink(const SessionContext *c) { |
1095 | /* Since PID 1 currently doesn't do Varlink right now, we cannot directly set properties for the | |
1096 | * scope, for now. */ | |
1097 | return !c->memory_max && | |
1098 | !c->runtime_max_sec && | |
1099 | !c->tasks_max && | |
1100 | !c->cpu_weight && | |
1101 | !c->io_weight; | |
1102 | } | |
1103 | ||
32580792 LP |
1104 | static int register_session( |
1105 | pam_handle_t *handle, | |
1106 | SessionContext *c, | |
1107 | UserRecord *ur, | |
1108 | bool debug, | |
cfb7abc7 | 1109 | char **ret_seat, |
d8069b8a | 1110 | char **ret_type, |
cfb7abc7 | 1111 | char **ret_runtime_dir) { |
32580792 | 1112 | |
32580792 LP |
1113 | int r; |
1114 | ||
1115 | assert(handle); | |
1116 | assert(c); | |
1117 | assert(ur); | |
1118 | assert(ret_seat); | |
d8069b8a | 1119 | assert(ret_type); |
cfb7abc7 | 1120 | assert(ret_runtime_dir); |
32580792 | 1121 | |
90ee2c59 RP |
1122 | /* We don't register session class none with logind */ |
1123 | if (streq(c->class, "none")) { | |
4582f8d7 | 1124 | pam_debug_syslog(handle, debug, "Skipping logind registration for session class none."); |
d8069b8a | 1125 | *ret_seat = *ret_type = *ret_runtime_dir = NULL; |
4582f8d7 | 1126 | return PAM_SUCCESS; |
90ee2c59 RP |
1127 | } |
1128 | ||
32580792 | 1129 | /* Make most of this a NOP on non-logind systems */ |
4582f8d7 LP |
1130 | if (!logind_running()) { |
1131 | pam_debug_syslog(handle, debug, "Skipping logind registration as logind is not running."); | |
d8069b8a | 1132 | *ret_seat = *ret_type = *ret_runtime_dir = NULL; |
4582f8d7 LP |
1133 | return PAM_SUCCESS; |
1134 | } | |
cc377381 | 1135 | |
27ccba26 ZJS |
1136 | pam_debug_syslog(handle, debug, |
1137 | "Asking logind to create session: " | |
1138 | "uid="UID_FMT" pid="PID_FMT" service=%s type=%s class=%s desktop=%s seat=%s vtnr=%"PRIu32" tty=%s display=%s remote=%s remote_user=%s remote_host=%s", | |
1139 | ur->uid, getpid_cached(), | |
32580792 LP |
1140 | strempty(c->service), |
1141 | c->type, c->class, strempty(c->desktop), | |
1142 | strempty(c->seat), c->vtnr, strempty(c->tty), strempty(c->display), | |
1143 | yes_no(c->remote), strempty(c->remote_user), strempty(c->remote_host)); | |
27ccba26 ZJS |
1144 | pam_debug_syslog(handle, debug, |
1145 | "Session limits: " | |
1146 | "memory_max=%s tasks_max=%s cpu_weight=%s io_weight=%s runtime_max_sec=%s", | |
32580792 | 1147 | strna(c->memory_max), strna(c->tasks_max), strna(c->cpu_weight), strna(c->io_weight), strna(c->runtime_max_sec)); |
76f2191d | 1148 | |
4582f8d7 LP |
1149 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; /* the following variables point into this message, hence pin it for longer */ |
1150 | _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; /* similar */ | |
1151 | const char *id = NULL, *object_path = NULL, *runtime_path = NULL, *real_seat = NULL; | |
3180c4d4 | 1152 | int existing = false; |
4582f8d7 | 1153 | uint32_t original_uid = UID_INVALID, real_vtnr = 0; |
ce959314 | 1154 | |
4582f8d7 LP |
1155 | bool done = false; |
1156 | if (can_use_varlink(c)) { | |
1157 | ||
1158 | r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.Login"); | |
1159 | if (r < 0) | |
1160 | log_debug_errno(r, "Failed to connect to logind via Varlink, falling back to D-Bus: %m"); | |
1161 | else { | |
4582f8d7 LP |
1162 | r = sd_varlink_set_allow_fd_passing_output(vl, true); |
1163 | if (r < 0) | |
1164 | return pam_syslog_errno(handle, LOG_ERR, r, "Failed to enable output fd passing on Varlink socket: %m"); | |
1165 | ||
1166 | r = sd_varlink_set_relative_timeout(vl, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC); | |
1167 | if (r < 0) | |
1168 | return pam_syslog_errno(handle, LOG_ERR, r, "Failed to set relative timeout on Varlink socket: %m"); | |
1169 | ||
1170 | _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; | |
1171 | r = pidref_set_self(&pidref); | |
1172 | if (r < 0) | |
1173 | return pam_syslog_errno(handle, LOG_ERR, r, "Failed to acquire PID reference on ourselves: %m"); | |
1174 | ||
1175 | sd_json_variant *vreply = NULL; | |
1176 | const char *error_id = NULL; | |
1177 | r = sd_varlink_callbo( | |
1178 | vl, | |
1179 | "io.systemd.Login.CreateSession", | |
1180 | &vreply, | |
1181 | &error_id, | |
1182 | SD_JSON_BUILD_PAIR_UNSIGNED("UID", ur->uid), | |
1183 | JSON_BUILD_PAIR_PIDREF("PID", &pidref), | |
1184 | JSON_BUILD_PAIR_STRING_NON_EMPTY("Service", c->service), | |
1185 | SD_JSON_BUILD_PAIR("Type", JSON_BUILD_STRING_UNDERSCORIFY(c->type)), | |
1186 | SD_JSON_BUILD_PAIR("Class", JSON_BUILD_STRING_UNDERSCORIFY(c->class)), | |
1187 | JSON_BUILD_PAIR_STRING_NON_EMPTY("Desktop", c->desktop), | |
1188 | JSON_BUILD_PAIR_STRING_NON_EMPTY("Seat", c->seat), | |
1189 | SD_JSON_BUILD_PAIR_CONDITION(c->vtnr > 0, "VTNr", SD_JSON_BUILD_UNSIGNED(c->vtnr)), | |
1190 | JSON_BUILD_PAIR_STRING_NON_EMPTY("TTY", c->tty), | |
1191 | JSON_BUILD_PAIR_STRING_NON_EMPTY("Display", c->display), | |
1192 | SD_JSON_BUILD_PAIR_BOOLEAN("Remote", c->remote), | |
1193 | JSON_BUILD_PAIR_STRING_NON_EMPTY("RemoteUser", c->remote_user), | |
1194 | JSON_BUILD_PAIR_STRING_NON_EMPTY("RemoteHost", c->remote_host)); | |
1195 | if (r < 0) | |
1196 | return pam_syslog_errno(handle, LOG_ERR, r, | |
1197 | "Failed to register session: %s", error_id); | |
1198 | if (streq_ptr(error_id, "io.systemd.Login.AlreadySessionMember")) { | |
1199 | /* We are already in a session, don't do anything */ | |
1200 | pam_debug_syslog(handle, debug, "Not creating session: %s", error_id); | |
d8069b8a | 1201 | *ret_seat = *ret_type= *ret_runtime_dir = NULL; |
4582f8d7 LP |
1202 | return PAM_SUCCESS; |
1203 | } | |
1204 | if (error_id) | |
1205 | return pam_syslog_errno(handle, LOG_ERR, sd_varlink_error_to_errno(error_id, vreply), | |
1206 | "Failed to issue CreateSession() varlink call: %s", error_id); | |
1207 | ||
1208 | struct { | |
1209 | const char *id; | |
1210 | const char *runtime_path; | |
4582f8d7 LP |
1211 | uid_t uid; |
1212 | const char *seat; | |
1213 | unsigned vtnr; | |
1214 | bool existing; | |
1215 | } p = { | |
4582f8d7 LP |
1216 | .uid = UID_INVALID, |
1217 | }; | |
1218 | ||
1219 | static const sd_json_dispatch_field dispatch_table[] = { | |
1220 | { "Id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, id), SD_JSON_MANDATORY }, | |
1221 | { "RuntimePath", SD_JSON_VARIANT_STRING, json_dispatch_const_path, voffsetof(p, runtime_path), SD_JSON_MANDATORY }, | |
4582f8d7 LP |
1222 | { "UID", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, voffsetof(p, uid), SD_JSON_MANDATORY }, |
1223 | { "Seat", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, seat), 0 }, | |
1224 | { "VTNr", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, voffsetof(p, vtnr), 0 }, | |
1225 | {} | |
1226 | }; | |
1227 | ||
1228 | r = sd_json_dispatch(vreply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); | |
1229 | if (r < 0) | |
1230 | return pam_syslog_errno(handle, LOG_ERR, r, "Failed to parse CreateSession() reply: %m"); | |
1231 | ||
4582f8d7 LP |
1232 | id = p.id; |
1233 | runtime_path = p.runtime_path; | |
1234 | original_uid = p.uid; | |
1235 | real_seat = p.seat; | |
1236 | real_vtnr = p.vtnr; | |
1237 | existing = false; /* Even on D-Bus logind only returns false these days */ | |
1238 | ||
1239 | done = true; | |
1240 | } | |
1241 | } | |
1242 | ||
1243 | if (!done) { | |
1244 | /* Let's release the D-Bus connection once we are done here, after all the session might live | |
1245 | * quite a long time, and we are not going to process the bus connection in that time, so | |
1246 | * let's better close before the daemon kicks us off because we are not processing | |
1247 | * anything. */ | |
1248 | _cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL; | |
1249 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; | |
1250 | ||
1251 | /* Talk to logind over the message bus */ | |
1252 | r = pam_acquire_bus_connection(handle, "pam-systemd", debug, &bus, &d); | |
1253 | if (r != PAM_SUCCESS) | |
1254 | return r; | |
1255 | ||
1256 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; | |
1257 | r = create_session_message( | |
1258 | bus, | |
1259 | handle, | |
1260 | ur, | |
1261 | c, | |
1262 | /* avoid_pidfd = */ false, | |
1263 | &m); | |
29488031 MY |
1264 | if (r < 0) |
1265 | return pam_bus_log_create_error(handle, r); | |
1266 | ||
4582f8d7 | 1267 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
29488031 | 1268 | r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply); |
4582f8d7 LP |
1269 | if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) { |
1270 | sd_bus_error_free(&error); | |
27ccba26 | 1271 | pam_debug_syslog(handle, debug, |
4582f8d7 LP |
1272 | "CreateSessionWithPIDFD() API is not available, retrying with CreateSession()."); |
1273 | ||
1274 | m = sd_bus_message_unref(m); | |
1275 | r = create_session_message(bus, | |
1276 | handle, | |
1277 | ur, | |
1278 | c, | |
1279 | /* avoid_pidfd = */ true, | |
1280 | &m); | |
1281 | if (r < 0) | |
1282 | return pam_bus_log_create_error(handle, r); | |
1283 | ||
1284 | r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply); | |
1285 | } | |
1286 | if (r < 0) { | |
1287 | if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) { | |
1288 | /* We are already in a session, don't do anything */ | |
1289 | pam_debug_syslog(handle, debug, | |
1290 | "Not creating session: %s", bus_error_message(&error, r)); | |
d8069b8a | 1291 | *ret_seat = *ret_type = *ret_runtime_dir = NULL; |
4582f8d7 LP |
1292 | return PAM_SUCCESS; |
1293 | } | |
1294 | ||
1295 | pam_syslog(handle, LOG_ERR, | |
1296 | "Failed to create session: %s", bus_error_message(&error, r)); | |
1297 | return PAM_SESSION_ERR; | |
b80120c4 | 1298 | } |
29488031 | 1299 | |
4582f8d7 LP |
1300 | r = sd_bus_message_read( |
1301 | reply, | |
1302 | "soshusub", | |
1303 | &id, | |
1304 | &object_path, | |
1305 | &runtime_path, | |
3180c4d4 | 1306 | /* session_fd = */ NULL, |
4582f8d7 LP |
1307 | &original_uid, |
1308 | &real_seat, | |
1309 | &real_vtnr, | |
1310 | &existing); | |
1311 | if (r < 0) | |
1312 | return pam_bus_log_parse_error(handle, r); | |
98a28fef | 1313 | } |
74fe1fe3 | 1314 | |
27ccba26 ZJS |
1315 | pam_debug_syslog(handle, debug, |
1316 | "Reply from logind: " | |
3180c4d4 MY |
1317 | "id=%s object_path=%s runtime_path=%s seat=%s vtnr=%u original_uid=%u", |
1318 | id, strna(object_path), runtime_path, real_seat, real_vtnr, original_uid); | |
ce959314 | 1319 | |
0ba24952 MY |
1320 | /* Please update manager_default_environment() in core/manager.c accordingly if more session envvars |
1321 | * shall be added. */ | |
1322 | ||
d6baaa69 LP |
1323 | r = update_environment(handle, "XDG_SESSION_ID", id); |
1324 | if (r != PAM_SUCCESS) | |
5a330cda | 1325 | return r; |
8c6db833 | 1326 | |
b2f74f07 LP |
1327 | /* Most likely we got the session/type/class from environment variables, but might have gotten the data |
1328 | * somewhere else (for example PAM module parameters). Let's now update the environment variables, so that this | |
1329 | * data is inherited into the session processes, and programs can rely on them to be initialized. */ | |
1330 | ||
d8069b8a LP |
1331 | _cleanup_free_ char *real_type = strdup(c->type); /* make copy because this might point to env block, which we are going to update shortly */ |
1332 | if (!real_type) | |
1333 | return pam_log_oom(handle); | |
1334 | ||
32580792 | 1335 | r = update_environment(handle, "XDG_SESSION_TYPE", c->type); |
b2f74f07 LP |
1336 | if (r != PAM_SUCCESS) |
1337 | return r; | |
1338 | ||
32580792 | 1339 | r = update_environment(handle, "XDG_SESSION_CLASS", c->class); |
b2f74f07 LP |
1340 | if (r != PAM_SUCCESS) |
1341 | return r; | |
1342 | ||
32580792 | 1343 | r = update_environment(handle, "XDG_SESSION_DESKTOP", c->desktop); |
b2f74f07 LP |
1344 | if (r != PAM_SUCCESS) |
1345 | return r; | |
1346 | ||
32580792 | 1347 | r = update_environment(handle, "XDG_SEAT", real_seat); |
d6baaa69 LP |
1348 | if (r != PAM_SUCCESS) |
1349 | return r; | |
bbc73283 | 1350 | |
32580792 LP |
1351 | if (real_vtnr > 0) { |
1352 | char buf[DECIMAL_STR_MAX(real_vtnr)]; | |
1353 | xsprintf(buf, "%u", real_vtnr); | |
bbc73283 | 1354 | |
d6baaa69 LP |
1355 | r = update_environment(handle, "XDG_VTNR", buf); |
1356 | if (r != PAM_SUCCESS) | |
5a330cda | 1357 | return r; |
bbc73283 LP |
1358 | } |
1359 | ||
77085881 | 1360 | r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL); |
f48e9376 ZJS |
1361 | if (r != PAM_SUCCESS) |
1362 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to install existing flag: @PAMERR@"); | |
77085881 | 1363 | |
cfb7abc7 LP |
1364 | /* Don't set $XDG_RUNTIME_DIR if the user we now authenticated for does not match the |
1365 | * original user of the session. We do this in order not to result in privileged apps | |
1366 | * clobbering the runtime directory unnecessarily. */ | |
1367 | _cleanup_free_ char *rt = NULL; | |
1368 | if (original_uid == ur->uid && validate_runtime_directory(handle, runtime_path, ur->uid)) | |
1369 | if (strdup_to(&rt, runtime_path) < 0) | |
1370 | return pam_log_oom(handle); | |
1371 | ||
32580792 LP |
1372 | /* Everything worked, hence let's patch in the data we learned. Since 'real_set' points into the |
1373 | * D-Bus message, let's copy it and return it as a buffer */ | |
cfb7abc7 LP |
1374 | _cleanup_free_ char *rs = NULL; |
1375 | if (strdup_to(&rs, real_seat) < 0) | |
1376 | return pam_log_oom(handle); | |
32580792 | 1377 | |
32580792 | 1378 | c->vtnr = real_vtnr; |
cfb7abc7 | 1379 | c->seat = *ret_seat = TAKE_PTR(rs); |
d8069b8a | 1380 | c->type = *ret_type = TAKE_PTR(real_type); |
cfb7abc7 LP |
1381 | *ret_runtime_dir = TAKE_PTR(rt); |
1382 | ||
32580792 LP |
1383 | return PAM_SUCCESS; |
1384 | } | |
1385 | ||
1386 | static int import_shell_credentials(pam_handle_t *handle) { | |
1387 | ||
1388 | static const char *const propagate[] = { | |
1389 | "shell.prompt.prefix", "SHELL_PROMPT_PREFIX", | |
1390 | "shell.prompt.suffix", "SHELL_PROMPT_SUFFIX", | |
1391 | "shell.welcome", "SHELL_WELCOME", | |
1392 | NULL | |
1393 | }; | |
1394 | int r; | |
1395 | ||
1396 | assert(handle); | |
1397 | ||
1398 | STRV_FOREACH_PAIR(k, v, propagate) { | |
1399 | r = propagate_credential_to_environment(handle, *k, *v); | |
1400 | if (r != PAM_SUCCESS) | |
1401 | return r; | |
1402 | } | |
1403 | ||
1404 | return PAM_SUCCESS; | |
1405 | } | |
1406 | ||
cfb7abc7 LP |
1407 | static int mkdir_chown_open_directory( |
1408 | int parent_fd, | |
1409 | const char *name, | |
1410 | uid_t uid, | |
1411 | gid_t gid, | |
1412 | mode_t mode) { | |
1413 | ||
1414 | _cleanup_free_ char *t = NULL; | |
1415 | int r; | |
1416 | ||
1417 | assert(parent_fd >= 0); | |
1418 | assert(name); | |
1419 | assert(uid_is_valid(uid)); | |
1420 | assert(gid_is_valid(gid)); | |
1421 | assert(mode != MODE_INVALID); | |
1422 | ||
1423 | for (unsigned attempt = 0;; attempt++) { | |
1424 | _cleanup_close_ int fd = openat(parent_fd, name, O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); | |
1425 | if (fd >= 0) | |
1426 | return TAKE_FD(fd); | |
1427 | if (errno != ENOENT) | |
1428 | return -errno; | |
1429 | ||
1430 | /* Let's create the directory under a temporary name first, since we want to make sure that | |
1431 | * once it appears under the right name it has the right ownership */ | |
1432 | r = tempfn_random(name, /* extra= */ NULL, &t); | |
1433 | if (r < 0) | |
1434 | return r; | |
1435 | ||
1436 | fd = open_mkdir_at(parent_fd, t, O_CLOEXEC|O_EXCL, 0700); /* Use restrictive mode until ownership is in order */ | |
1437 | if (fd < 0) | |
1438 | return fd; | |
1439 | ||
1440 | r = RET_NERRNO(fchown(fd, uid, gid)); | |
1441 | if (r < 0) | |
1442 | goto fail; | |
1443 | ||
1444 | r = RET_NERRNO(fchmod(fd, mode)); | |
1445 | if (r < 0) | |
1446 | goto fail; | |
1447 | ||
1448 | r = rename_noreplace(parent_fd, t, parent_fd, name); | |
1449 | if (r >= 0) | |
1450 | return TAKE_FD(fd); | |
1451 | if (r != -EEXIST || attempt >= 5) | |
1452 | goto fail; | |
1453 | ||
1454 | /* Maybe some other login attempt created the directory at the same time? Let's retry */ | |
1455 | (void) unlinkat(parent_fd, t, AT_REMOVEDIR); | |
1456 | t = mfree(t); | |
1457 | } | |
1458 | ||
1459 | fail: | |
1460 | (void) unlinkat(parent_fd, ASSERT_PTR(t), AT_REMOVEDIR); | |
1461 | return r; | |
1462 | } | |
1463 | ||
1464 | static int make_area_runtime_directory( | |
1465 | pam_handle_t *handle, | |
1466 | UserRecord *ur, | |
1467 | const char *runtime_directory, | |
1468 | const char *area, | |
1469 | char **ret) { | |
1470 | ||
1471 | assert(handle); | |
1472 | assert(ur); | |
1473 | assert(runtime_directory); | |
1474 | assert(area); | |
1475 | assert(ret); | |
1476 | ||
1477 | /* Let's be careful with creating these directories, the runtime directory is owned by the user after all, | |
1478 | * and they might play symlink games with us. */ | |
1479 | ||
1480 | _cleanup_close_ int fd = open(runtime_directory, O_CLOEXEC|O_PATH|O_DIRECTORY); | |
1481 | if (fd < 0) | |
1482 | return pam_syslog_errno(handle, LOG_ERR, errno, "Unable to open runtime directory '%s': %m", runtime_directory); | |
1483 | ||
1484 | _cleanup_close_ int fd_areas = mkdir_chown_open_directory(fd, "Areas", ur->uid, user_record_gid(ur), 0755); | |
1485 | if (fd_areas < 0) | |
1486 | return pam_syslog_errno(handle, LOG_ERR, fd_areas, "Unable to create 'Areas' directory below '%s': %m", runtime_directory); | |
1487 | ||
1488 | _cleanup_close_ int fd_area = mkdir_chown_open_directory(fd_areas, area, ur->uid, user_record_gid(ur), 0755); | |
1489 | if (fd_area < 0) | |
1490 | return pam_syslog_errno(handle, LOG_ERR, fd_area, "Unable to create '%s' directory below '%s/Areas': %m", area, runtime_directory); | |
1491 | ||
1492 | char *j = path_join(runtime_directory, "Areas", area); | |
1493 | if (!j) | |
1494 | return pam_log_oom(handle); | |
1495 | ||
1496 | *ret = j; | |
1497 | return 0; | |
1498 | } | |
1499 | ||
1500 | static int setup_environment( | |
c747c041 LP |
1501 | pam_handle_t *handle, |
1502 | UserRecord *ur, | |
cfb7abc7 | 1503 | const char *runtime_directory, |
c747c041 LP |
1504 | const char *area, |
1505 | bool debug) { | |
1506 | ||
1507 | int r; | |
1508 | ||
1509 | assert(handle); | |
1510 | assert(ur); | |
1511 | ||
1512 | const char *h = ASSERT_PTR(user_record_home_directory(ur)); | |
1513 | ||
1514 | /* If an empty area string is specified, this means an explicit: do not use the area logic, normalize this here */ | |
1515 | area = empty_to_null(area); | |
1516 | ||
cfb7abc7 | 1517 | _cleanup_free_ char *ha = NULL, *area_copy = NULL; |
c747c041 LP |
1518 | if (area) { |
1519 | _cleanup_free_ char *j = path_join(h, "Areas", area); | |
1520 | if (!j) | |
1521 | return pam_log_oom(handle); | |
1522 | ||
1523 | _cleanup_close_ int fd = -EBADF; | |
1524 | r = chase(j, /* root= */ NULL, CHASE_MUST_BE_DIRECTORY, &ha, &fd); | |
1525 | if (r < 0) { | |
1526 | /* Log the precise error */ | |
1527 | pam_syslog_errno(handle, LOG_WARNING, r, "Path '%s' of requested user area '%s' is not accessible, reverting to regular home directory: %m", j, area); | |
1528 | ||
1529 | /* Also tell the user directly at login, but a bit more vague */ | |
1530 | pam_info(handle, "Path '%s' of requested user area '%s' is not accessible, reverting to regular home directory.", j, area); | |
1531 | area = NULL; | |
1532 | } else { | |
1533 | /* Validate that the target is definitely owned by user */ | |
1534 | struct stat st; | |
1535 | if (fstat(fd, &st) < 0) | |
1536 | return pam_syslog_errno(handle, LOG_ERR, errno, "Unable to fstat() target area directory '%s': %m", ha); | |
1537 | ||
1538 | if (st.st_uid != ur->uid) { | |
1539 | pam_syslog(handle, LOG_ERR, "Path '%s' of requested user area '%s' is not owned by user, reverting to regular home directory.", ha, area); | |
1540 | ||
1541 | /* Also tell the user directly at login. */ | |
1542 | pam_info(handle, "Path '%s' of requested user area '%s' is not owned by user, reverting to regular home directory.", ha, area); | |
1543 | area = NULL; | |
1544 | } else { | |
cfb7abc7 LP |
1545 | /* All good, now make a copy of the area string, since we quite likely are |
1546 | * going to invalidate it (if it points into the environment block), via the | |
1547 | * update_environment() call below */ | |
1548 | area_copy = strdup(area); | |
1549 | if (!area_copy) | |
1550 | return pam_log_oom(handle); | |
1551 | ||
1552 | pam_debug_syslog(handle, debug, "Area '%s' selected, setting $HOME to '%s'.", area, ha); | |
c747c041 | 1553 | h = ha; |
cfb7abc7 | 1554 | area = area_copy; |
c747c041 LP |
1555 | } |
1556 | } | |
1557 | } | |
1558 | ||
cfb7abc7 LP |
1559 | r = update_environment(handle, "XDG_AREA", area); |
1560 | if (r != PAM_SUCCESS) | |
1561 | return r; | |
1562 | ||
1563 | r = update_environment(handle, "HOME", h); | |
1564 | if (r != PAM_SUCCESS) | |
1565 | return r; | |
1566 | ||
1567 | _cleanup_free_ char *per_area_runtime_directory = NULL; | |
1568 | if (runtime_directory && area) { | |
1569 | /* Also create a per-area subdirectory for $XDG_RUNTIME_DIR, so that each area has their own | |
1570 | * set of runtime services. We follow the same directory structure as for $HOME. Note that we | |
1571 | * do not define any form of automatic clean-up for the per-aera subdirs beyond the regular | |
1572 | * clean-up of the whole $XDG_RUNTIME_DIRECTORY hierarchy when the user finally logs out. */ | |
1573 | ||
1574 | r = make_area_runtime_directory(handle, ur, runtime_directory, area, &per_area_runtime_directory); | |
c747c041 LP |
1575 | if (r != PAM_SUCCESS) |
1576 | return r; | |
cfb7abc7 LP |
1577 | |
1578 | runtime_directory = per_area_runtime_directory; | |
c747c041 LP |
1579 | } |
1580 | ||
cfb7abc7 LP |
1581 | r = update_environment(handle, "XDG_RUNTIME_DIR", runtime_directory); |
1582 | if (r != PAM_SUCCESS) | |
1583 | return r; | |
1584 | ||
1585 | return export_legacy_dbus_address(handle, runtime_directory); | |
c747c041 LP |
1586 | } |
1587 | ||
d8069b8a LP |
1588 | static int open_osc_context(pam_handle_t *handle, const char *session_type, UserRecord *ur) { |
1589 | int r; | |
1590 | ||
1591 | assert(handle); | |
1592 | assert(ur); | |
1593 | ||
1594 | /* If this is a TTY session, then output the session start OSC sequence */ | |
1595 | ||
1596 | if (!streq_ptr(session_type, "tty")) | |
1597 | return PAM_SUCCESS; | |
1598 | ||
1599 | const char *e = pam_getenv(handle, "TERM"); | |
1600 | if (!e) | |
1601 | e = getenv("TERM"); | |
1602 | if (streq_ptr(e, "dumb")) | |
1603 | return PAM_SUCCESS; | |
1604 | ||
1605 | /* NB: we output directly to stdout, instead of going via pam_info() or so, because that's too | |
1606 | * high-level for us, as it suffixes the output with a newline, expecting a full blown text message | |
1607 | * as prompt string, not just an ANSI sequence. Note that PAM's conv_misc() actually goes to stdout | |
1608 | * anyway, hence let's do so here too, but only after careful validation. */ | |
ffabfef9 | 1609 | if (!isatty_safe(STDOUT_FILENO)) |
d8069b8a LP |
1610 | return PAM_SUCCESS; |
1611 | ||
1612 | /* Keep a reference to the TTY we are operating on, so that we can issue the OSC close sequence also | |
1613 | * if the TTY is already closed. We use an O_PATH reference here, rather than a properly opened fd, | |
1614 | * so that we don't delay tty hang-up. */ | |
1615 | _cleanup_close_ int tty_opath_fd = fd_reopen(STDOUT_FILENO, O_PATH|O_CLOEXEC); | |
1616 | if (tty_opath_fd < 0) | |
1617 | pam_syslog_errno(handle, LOG_DEBUG, tty_opath_fd, "Failed to pin TTY, ignoring: %m"); | |
1618 | else | |
1619 | tty_opath_fd = fd_move_above_stdio(tty_opath_fd); | |
1620 | ||
1621 | _cleanup_free_ char *osc = NULL; | |
1622 | sd_id128_t osc_id; | |
1623 | r = osc_context_open_session( | |
1624 | ur->user_name, | |
1625 | pam_getenv(handle, "XDG_SESSION_ID"), | |
1626 | &osc, | |
1627 | &osc_id); | |
1628 | if (r < 0) | |
1629 | return pam_syslog_errno(handle, LOG_ERR, r, "Failed to prepare OSC sequence: %m"); | |
1630 | ||
1631 | r = loop_write(STDOUT_FILENO, osc, SIZE_MAX); | |
1632 | if (r < 0) | |
1633 | return pam_syslog_errno(handle, LOG_ERR, r, "Failed to write OSC sequence to TTY: %m"); | |
1634 | ||
1635 | /* Remember the OSC context id, so that we can close it cleanly later */ | |
1636 | _cleanup_free_ sd_id128_t *osc_id_copy = newdup(sd_id128_t, &osc_id, 1); | |
1637 | if (!osc_id_copy) | |
1638 | return pam_log_oom(handle); | |
1639 | ||
1640 | r = pam_set_data(handle, "systemd.osc-context-id", osc_id_copy, pam_cleanup_free); | |
1641 | if (r != PAM_SUCCESS) | |
1642 | return pam_syslog_pam_error(handle, LOG_ERR, r, | |
1643 | "Failed to set PAM OSC sequence ID data: @PAMERR@"); | |
1644 | ||
1645 | TAKE_PTR(osc_id_copy); | |
1646 | ||
1647 | if (tty_opath_fd >= 0) { | |
1648 | r = pam_set_data(handle, "systemd.osc-context-fd", FD_TO_PTR(tty_opath_fd), pam_cleanup_close); | |
1649 | if (r != PAM_SUCCESS) | |
1650 | return pam_syslog_pam_error(handle, LOG_ERR, r, | |
1651 | "Failed to set PAM OSC sequence fd data: @PAMERR@"); | |
1652 | ||
1653 | TAKE_FD(tty_opath_fd); | |
1654 | } | |
1655 | ||
1656 | return PAM_SUCCESS; | |
1657 | } | |
1658 | ||
1659 | static int close_osc_context(pam_handle_t *handle) { | |
1660 | int r; | |
1661 | ||
1662 | assert(handle); | |
1663 | ||
1664 | const void *p; | |
1665 | int tty_opath_fd = -EBADF; | |
1666 | r = pam_get_data(handle, "systemd.osc-context-fd", &p); | |
1667 | if (r == PAM_SUCCESS) | |
1668 | tty_opath_fd = PTR_TO_FD(p); | |
1669 | else if (r != PAM_NO_MODULE_DATA) | |
1670 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM OSC context fd: @PAMERR@"); | |
1671 | if (tty_opath_fd < 0) | |
1672 | return PAM_SUCCESS; | |
1673 | ||
1674 | const sd_id128_t *osc_id = NULL; | |
1675 | r = pam_get_data(handle, "systemd.osc-context-id", (const void**) &osc_id); | |
1676 | if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) | |
1677 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM OSC context id data: @PAMERR@"); | |
1678 | if (!osc_id) | |
1679 | return PAM_SUCCESS; | |
1680 | ||
1681 | /* Now open the original TTY again, so that we can write on it */ | |
1682 | _cleanup_close_ int fd = fd_reopen(tty_opath_fd, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); | |
1683 | if (fd < 0) { | |
1684 | pam_syslog_errno(handle, LOG_DEBUG, fd, "Failed to reopen TTY, ignoring: %m"); | |
1685 | return PAM_SUCCESS; | |
1686 | } | |
1687 | ||
1688 | /* /bin/login calls us with fds 0, 1, 2 closed, which is just weird. Let's step outside of that | |
1689 | * range, just in case pam_syslog() or so logs to stderr */ | |
1690 | fd = fd_move_above_stdio(fd); | |
1691 | ||
1692 | /* Safety check, let's verify this is a valid TTY we just opened */ | |
ffabfef9 | 1693 | if (!isatty_safe(fd)) |
d8069b8a LP |
1694 | return PAM_SUCCESS; |
1695 | ||
1696 | _cleanup_free_ char *osc = NULL; | |
1697 | r = osc_context_close(*osc_id, &osc); | |
1698 | if (r < 0) | |
1699 | return pam_syslog_errno(handle, LOG_ERR, r, "Failed to prepare OSC sequence: %m"); | |
1700 | ||
1701 | r = loop_write(fd, osc, SIZE_MAX); | |
1702 | if (r < 0) | |
1703 | return pam_syslog_errno(handle, LOG_ERR, r, "Failed to write OSC sequence to TTY: %m"); | |
1704 | ||
1705 | return PAM_SUCCESS; | |
1706 | } | |
1707 | ||
32580792 LP |
1708 | _public_ PAM_EXTERN int pam_sm_open_session( |
1709 | pam_handle_t *handle, | |
1710 | int flags, | |
1711 | int argc, const char **argv) { | |
1712 | ||
1713 | int r; | |
1714 | ||
1715 | assert(handle); | |
1716 | ||
1717 | pam_log_setup(); | |
1718 | ||
1719 | uint64_t default_capability_bounding_set = UINT64_MAX, default_capability_ambient_set = UINT64_MAX; | |
c747c041 | 1720 | const char *class_pam = NULL, *type_pam = NULL, *desktop_pam = NULL, *area_pam = NULL; |
32580792 LP |
1721 | bool debug = false; |
1722 | if (parse_argv(handle, | |
1723 | argc, argv, | |
1724 | &class_pam, | |
1725 | &type_pam, | |
1726 | &desktop_pam, | |
c747c041 | 1727 | &area_pam, |
32580792 LP |
1728 | &debug, |
1729 | &default_capability_bounding_set, | |
1730 | &default_capability_ambient_set) < 0) | |
1731 | return PAM_SESSION_ERR; | |
1732 | ||
1733 | pam_debug_syslog(handle, debug, "pam-systemd initializing"); | |
1734 | ||
1735 | _cleanup_(user_record_unrefp) UserRecord *ur = NULL; | |
1736 | r = acquire_user_record(handle, &ur); | |
1737 | if (r != PAM_SUCCESS) | |
1738 | return r; | |
1739 | ||
1740 | SessionContext c = {}; | |
1741 | r = pam_get_item_many( | |
1742 | handle, | |
f07fe275 | 1743 | PAM_SERVICE, &c.service, |
32580792 | 1744 | PAM_XDISPLAY, &c.display, |
f07fe275 LP |
1745 | PAM_TTY, &c.tty, |
1746 | PAM_RUSER, &c.remote_user, | |
1747 | PAM_RHOST, &c.remote_host); | |
32580792 LP |
1748 | if (r != PAM_SUCCESS) |
1749 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM items: @PAMERR@"); | |
1750 | ||
1751 | c.seat = getenv_harder(handle, "XDG_SEAT", NULL); | |
1752 | c.vtnr = getenv_harder_uint32(handle, "XDG_VTNR", 0); | |
1753 | c.type = getenv_harder(handle, "XDG_SESSION_TYPE", type_pam); | |
1754 | c.class = getenv_harder(handle, "XDG_SESSION_CLASS", class_pam); | |
1755 | c.desktop = getenv_harder(handle, "XDG_SESSION_DESKTOP", desktop_pam); | |
c747c041 | 1756 | c.area = getenv_harder(handle, "XDG_AREA", area_pam); |
32580792 LP |
1757 | c.incomplete = getenv_harder_bool(handle, "XDG_SESSION_INCOMPLETE", false); |
1758 | ||
f07fe275 LP |
1759 | r = pam_get_data_many( |
1760 | handle, | |
1761 | "systemd.memory_max", &c.memory_max, | |
1762 | "systemd.tasks_max", &c.tasks_max, | |
1763 | "systemd.cpu_weight", &c.cpu_weight, | |
1764 | "systemd.io_weight", &c.io_weight, | |
1765 | "systemd.runtime_max_sec", &c.runtime_max_sec); | |
1766 | if (r != PAM_SUCCESS) | |
1767 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM data: @PAMERR@"); | |
32580792 LP |
1768 | |
1769 | session_context_mangle(handle, &c, ur, debug); | |
1770 | ||
d8069b8a LP |
1771 | _cleanup_free_ char *seat_buffer = NULL, *type_buffer = NULL, *runtime_dir = NULL; |
1772 | r = register_session(handle, &c, ur, debug, &seat_buffer, &type_buffer, &runtime_dir); | |
32580792 LP |
1773 | if (r != PAM_SUCCESS) |
1774 | return r; | |
1775 | ||
166a678f LP |
1776 | r = import_shell_credentials(handle); |
1777 | if (r != PAM_SUCCESS) | |
1778 | return r; | |
1779 | ||
cfb7abc7 | 1780 | r = setup_environment(handle, ur, runtime_dir, c.area, debug); |
c747c041 LP |
1781 | if (r != PAM_SUCCESS) |
1782 | return r; | |
1783 | ||
bf1b9ae4 | 1784 | if (default_capability_ambient_set == UINT64_MAX) |
32580792 | 1785 | default_capability_ambient_set = pick_default_capability_ambient_set(ur, c.service, c.seat); |
bf1b9ae4 | 1786 | |
d8069b8a LP |
1787 | r = apply_user_record_settings(handle, ur, debug, default_capability_bounding_set, default_capability_ambient_set); |
1788 | if (r != PAM_SUCCESS) | |
1789 | return r; | |
1790 | ||
1791 | return open_osc_context(handle, c.type, ur); | |
98a28fef | 1792 | } |
8c6db833 | 1793 | |
98a28fef LP |
1794 | _public_ PAM_EXTERN int pam_sm_close_session( |
1795 | pam_handle_t *handle, | |
1796 | int flags, | |
1797 | int argc, const char **argv) { | |
8c6db833 | 1798 | |
5f41d1f1 | 1799 | const void *existing = NULL; |
45c5fa25 | 1800 | bool debug = false; |
75c8e3cf | 1801 | const char *id; |
75c8e3cf | 1802 | int r; |
8c6db833 | 1803 | |
ffcfcb6b | 1804 | assert(handle); |
75c8e3cf | 1805 | |
4eae58b3 DDM |
1806 | pam_log_setup(); |
1807 | ||
45c5fa25 LP |
1808 | if (parse_argv(handle, |
1809 | argc, argv, | |
30de5691 LP |
1810 | /* class= */ NULL, |
1811 | /* type= */ NULL, | |
1970bf74 | 1812 | /* desktop= */ NULL, |
c747c041 | 1813 | /* area= */ NULL, |
bf1b9ae4 | 1814 | &debug, |
30de5691 LP |
1815 | /* default_capability_bounding_set */ NULL, |
1816 | /* default_capability_ambient_set= */ NULL) < 0) | |
45c5fa25 LP |
1817 | return PAM_SESSION_ERR; |
1818 | ||
27ccba26 | 1819 | pam_debug_syslog(handle, debug, "pam-systemd shutting down"); |
45c5fa25 | 1820 | |
77085881 LP |
1821 | /* Only release session if it wasn't pre-existing when we |
1822 | * tried to create it */ | |
148369de | 1823 | r = pam_get_data(handle, "systemd.existing", &existing); |
f48e9376 ZJS |
1824 | if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) |
1825 | return pam_syslog_pam_error(handle, LOG_ERR, r, | |
1826 | "Failed to get PAM systemd.existing data: @PAMERR@"); | |
77085881 | 1827 | |
d8069b8a LP |
1828 | (void) close_osc_context(handle); |
1829 | ||
75c8e3cf | 1830 | id = pam_getenv(handle, "XDG_SESSION_ID"); |
77085881 | 1831 | if (id && !existing) { |
4582f8d7 LP |
1832 | _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; |
1833 | bool done = false; | |
75c8e3cf | 1834 | |
4582f8d7 LP |
1835 | r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.Login"); |
1836 | if (r < 0) | |
1837 | log_debug_errno(r, "Failed to connect to logind via Varlink, falling back to D-Bus: %m"); | |
1838 | else { | |
1839 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *vreply = NULL; | |
1840 | const char *error_id = NULL; | |
1841 | r = sd_varlink_callbo( | |
1842 | vl, | |
1843 | "io.systemd.Login.ReleaseSession", | |
1844 | /* ret_reply= */ NULL, | |
1845 | &error_id, | |
1846 | SD_JSON_BUILD_PAIR_STRING("Id", id)); | |
1847 | if (r < 0) | |
1848 | return pam_syslog_errno(handle, LOG_ERR, r, "Failed to register session: %s", error_id); | |
1849 | if (error_id) | |
1850 | return pam_syslog_errno(handle, LOG_ERR, sd_varlink_error_to_errno(error_id, vreply), | |
1851 | "Failed to issue ReleaseSession() varlink call: %s", error_id); | |
75c8e3cf | 1852 | |
4582f8d7 LP |
1853 | done = true; |
1854 | } | |
75c8e3cf | 1855 | |
4582f8d7 LP |
1856 | if (!done) { |
1857 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
1858 | _cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL; | |
1859 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; | |
1860 | ||
1861 | /* Before we go and close the FIFO we need to tell logind that this is a clean session | |
1862 | * shutdown, so that it doesn't just go and slaughter us immediately after closing the fd */ | |
1863 | ||
1864 | r = pam_acquire_bus_connection(handle, "pam-systemd", debug, &bus, &d); | |
1865 | if (r != PAM_SUCCESS) | |
1866 | return r; | |
1867 | ||
1868 | r = bus_call_method(bus, bus_login_mgr, "ReleaseSession", &error, NULL, "s", id); | |
1869 | if (r < 0) | |
1870 | return pam_syslog_pam_error(handle, LOG_ERR, PAM_SESSION_ERR, | |
1871 | "Failed to release session: %s", bus_error_message(&error, r)); | |
1872 | } | |
75c8e3cf LP |
1873 | } |
1874 | ||
5f41d1f1 | 1875 | return PAM_SUCCESS; |
8c6db833 | 1876 | } |