]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
8c6db833 | 2 | |
00229fe4 | 3 | #include <endian.h> |
8c6db833 LP |
4 | #include <errno.h> |
5 | #include <fcntl.h> | |
8c6db833 | 6 | #include <pwd.h> |
8c6db833 | 7 | #include <security/_pam_macros.h> |
8c6db833 LP |
8 | #include <security/pam_ext.h> |
9 | #include <security/pam_misc.h> | |
00229fe4 LP |
10 | #include <security/pam_modules.h> |
11 | #include <security/pam_modutil.h> | |
12 | #include <sys/file.h> | |
ca78ad1d | 13 | #include <sys/stat.h> |
e21d9060 | 14 | #include <sys/sysmacros.h> |
ca78ad1d ZJS |
15 | #include <sys/types.h> |
16 | #include <unistd.h> | |
8c6db833 | 17 | |
b5efdb8a | 18 | #include "alloc-util.h" |
430f0182 | 19 | #include "audit-util.h" |
00229fe4 LP |
20 | #include "bus-common-errors.h" |
21 | #include "bus-error.h" | |
47094ce0 | 22 | #include "bus-internal.h" |
9b71e4ab | 23 | #include "bus-locator.h" |
bf1b9ae4 LP |
24 | #include "cap-list.h" |
25 | #include "capability-util.h" | |
fdb3deca | 26 | #include "cgroup-setup.h" |
3a4e4ffa | 27 | #include "devnum-util.h" |
4bbccb02 | 28 | #include "errno-util.h" |
3ffd4af2 | 29 | #include "fd-util.h" |
a5c32cff | 30 | #include "fileio.h" |
f97b34a6 | 31 | #include "format-util.h" |
f9c1f4e1 | 32 | #include "fs-util.h" |
958b66ea | 33 | #include "hostname-util.h" |
f9c1f4e1 | 34 | #include "locale-util.h" |
00229fe4 LP |
35 | #include "login-util.h" |
36 | #include "macro.h" | |
76f2191d | 37 | #include "missing_syscall.h" |
d750dde2 | 38 | #include "pam-util.h" |
6bedfcbb | 39 | #include "parse-util.h" |
e37e5ed3 | 40 | #include "path-util.h" |
ed5033fd | 41 | #include "percent-util.h" |
df0ff127 | 42 | #include "process-util.h" |
f9c1f4e1 | 43 | #include "rlimit-util.h" |
00229fe4 | 44 | #include "socket-util.h" |
055c08ef | 45 | #include "stdio-util.h" |
00229fe4 LP |
46 | #include "strv.h" |
47 | #include "terminal-util.h" | |
9ab0d3eb LP |
48 | #include "user-util.h" |
49 | #include "userdb.h" | |
8c6db833 | 50 | |
fbcb6300 LP |
51 | #define LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE) |
52 | ||
bf1b9ae4 LP |
53 | static int parse_caps( |
54 | pam_handle_t *handle, | |
55 | const char *value, | |
56 | uint64_t *caps) { | |
57 | ||
58 | bool subtract; | |
59 | int r; | |
60 | ||
61 | assert(handle); | |
62 | assert(value); | |
63 | ||
64 | if (value[0] == '~') { | |
65 | subtract = true; | |
66 | value++; | |
67 | } else | |
68 | subtract = false; | |
69 | ||
70 | for (;;) { | |
71 | _cleanup_free_ char *s = NULL; | |
72 | uint64_t b, m; | |
73 | int c; | |
74 | ||
75 | /* We can't use spaces as separators here, as PAM's simplistic argument parser doesn't allow | |
76 | * spaces inside of arguments. We use commas instead (which is similar to cap_from_text(), | |
77 | * which also uses commas). */ | |
78 | r = extract_first_word(&value, &s, ",", EXTRACT_DONT_COALESCE_SEPARATORS); | |
79 | if (r < 0) | |
80 | return r; | |
81 | if (r == 0) | |
82 | break; | |
83 | ||
84 | c = capability_from_name(s); | |
85 | if (c < 0) { | |
86 | pam_syslog(handle, LOG_WARNING, "Unknown capability, ignoring: %s", s); | |
87 | continue; | |
88 | } | |
89 | ||
90 | m = UINT64_C(1) << c; | |
91 | ||
92 | if (!caps) | |
93 | continue; | |
94 | ||
95 | if (*caps == UINT64_MAX) | |
96 | b = subtract ? all_capabilities() : 0; | |
97 | else | |
98 | b = *caps; | |
99 | ||
100 | if (subtract) | |
101 | *caps = b & ~m; | |
102 | else | |
103 | *caps = b | m; | |
104 | } | |
105 | ||
106 | return 0; | |
107 | } | |
108 | ||
baae0358 LP |
109 | static int parse_argv( |
110 | pam_handle_t *handle, | |
111 | int argc, const char **argv, | |
112 | const char **class, | |
49ebd11f | 113 | const char **type, |
f5cb2820 | 114 | const char **desktop, |
bf1b9ae4 LP |
115 | bool *debug, |
116 | uint64_t *default_capability_bounding_set, | |
117 | uint64_t *default_capability_ambient_set) { | |
8c6db833 | 118 | |
bf1b9ae4 | 119 | int r; |
8c6db833 | 120 | |
bf1b9ae4 | 121 | assert(handle); |
8c6db833 LP |
122 | assert(argc >= 0); |
123 | assert(argc == 0 || argv); | |
124 | ||
bf1b9ae4 | 125 | for (int i = 0; i < argc; i++) { |
d9608d40 LP |
126 | const char *p; |
127 | ||
128 | if ((p = startswith(argv[i], "class="))) { | |
485507b8 | 129 | if (class) |
d9608d40 | 130 | *class = p; |
485507b8 | 131 | |
d9608d40 | 132 | } else if ((p = startswith(argv[i], "type="))) { |
49ebd11f | 133 | if (type) |
d9608d40 | 134 | *type = p; |
49ebd11f | 135 | |
d9608d40 | 136 | } else if ((p = startswith(argv[i], "desktop="))) { |
f5cb2820 | 137 | if (desktop) |
d9608d40 | 138 | *desktop = p; |
f5cb2820 | 139 | |
05a049cc ZJS |
140 | } else if (streq(argv[i], "debug")) { |
141 | if (debug) | |
142 | *debug = true; | |
fb6becb4 | 143 | |
d9608d40 | 144 | } else if ((p = startswith(argv[i], "debug="))) { |
bf1b9ae4 LP |
145 | r = parse_boolean(p); |
146 | if (r < 0) | |
d9608d40 | 147 | pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", p); |
05a049cc | 148 | else if (debug) |
bf1b9ae4 LP |
149 | *debug = r; |
150 | ||
151 | } else if ((p = startswith(argv[i], "default-capability-bounding-set="))) { | |
152 | r = parse_caps(handle, p, default_capability_bounding_set); | |
153 | if (r < 0) | |
154 | pam_syslog(handle, LOG_WARNING, "Failed to parse default-capability-bounding-set= argument, ignoring: %s", p); | |
155 | ||
156 | } else if ((p = startswith(argv[i], "default-capability-ambient-set="))) { | |
157 | r = parse_caps(handle, p, default_capability_ambient_set); | |
158 | if (r < 0) | |
159 | pam_syslog(handle, LOG_WARNING, "Failed to parse default-capability-ambient-set= argument, ignoring: %s", p); | |
0e318cad | 160 | |
05a049cc | 161 | } else |
bf1b9ae4 | 162 | pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); |
49ebd11f | 163 | } |
8c6db833 | 164 | |
8c6db833 LP |
165 | return 0; |
166 | } | |
167 | ||
9ab0d3eb | 168 | static int acquire_user_record( |
8c6db833 | 169 | pam_handle_t *handle, |
9ab0d3eb | 170 | UserRecord **ret_record) { |
8c6db833 | 171 | |
9ab0d3eb LP |
172 | _cleanup_(user_record_unrefp) UserRecord *ur = NULL; |
173 | const char *username = NULL, *json = NULL; | |
dbe7fff4 | 174 | _cleanup_free_ char *field = NULL; |
8c6db833 LP |
175 | int r; |
176 | ||
177 | assert(handle); | |
8c6db833 | 178 | |
baae0358 | 179 | r = pam_get_user(handle, &username, NULL); |
f48e9376 ZJS |
180 | if (r != PAM_SUCCESS) |
181 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get user name: @PAMERR@"); | |
d90b9d27 | 182 | |
f48e9376 ZJS |
183 | if (isempty(username)) |
184 | return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, "User name not valid."); | |
8c6db833 | 185 | |
dbe7fff4 | 186 | /* If pam_systemd_homed (or some other module) already acquired the user record we can reuse it |
9ab0d3eb | 187 | * here. */ |
dbe7fff4 LP |
188 | field = strjoin("systemd-user-record-", username); |
189 | if (!field) | |
190 | return pam_log_oom(handle); | |
9ab0d3eb | 191 | |
dbe7fff4 | 192 | r = pam_get_data(handle, field, (const void**) &json); |
f48e9376 ZJS |
193 | if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) |
194 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM user record data: @PAMERR@"); | |
dbe7fff4 | 195 | if (r == PAM_SUCCESS && json) { |
9ab0d3eb LP |
196 | _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; |
197 | ||
198 | /* Parse cached record */ | |
199 | r = json_parse(json, JSON_PARSE_SENSITIVE, &v, NULL, NULL); | |
7e7b53b4 ZJS |
200 | if (r < 0) |
201 | return pam_syslog_errno(handle, LOG_ERR, r, "Failed to parse JSON user record: %m"); | |
9ab0d3eb LP |
202 | |
203 | ur = user_record_new(); | |
204 | if (!ur) | |
205 | return pam_log_oom(handle); | |
206 | ||
bfc0cc1a | 207 | r = user_record_load(ur, v, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE); |
7e7b53b4 ZJS |
208 | if (r < 0) |
209 | return pam_syslog_errno(handle, LOG_ERR, r, "Failed to load user record: %m"); | |
9ab0d3eb LP |
210 | |
211 | /* Safety check if cached record actually matches what we are looking for */ | |
f48e9376 ZJS |
212 | if (!streq_ptr(username, ur->user_name)) |
213 | return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, | |
214 | "Acquired user record does not match user name."); | |
dbe7fff4 LP |
215 | } else { |
216 | _cleanup_free_ char *formatted = NULL; | |
217 | ||
218 | /* Request the record ourselves */ | |
219 | r = userdb_by_name(username, 0, &ur); | |
220 | if (r < 0) { | |
7e7b53b4 | 221 | pam_syslog_errno(handle, LOG_ERR, r, "Failed to get user record: %m"); |
dbe7fff4 LP |
222 | return PAM_USER_UNKNOWN; |
223 | } | |
224 | ||
225 | r = json_variant_format(ur->json, 0, &formatted); | |
7e7b53b4 ZJS |
226 | if (r < 0) |
227 | return pam_syslog_errno(handle, LOG_ERR, r, "Failed to format user JSON: %m"); | |
dbe7fff4 LP |
228 | |
229 | /* And cache it for everyone else */ | |
230 | r = pam_set_data(handle, field, formatted, pam_cleanup_free); | |
f48e9376 ZJS |
231 | if (r != PAM_SUCCESS) |
232 | return pam_syslog_pam_error(handle, LOG_ERR, r, | |
233 | "Failed to set PAM user record data '%s': @PAMERR@", field); | |
dbe7fff4 | 234 | TAKE_PTR(formatted); |
9ab0d3eb LP |
235 | } |
236 | ||
f48e9376 ZJS |
237 | if (!uid_is_valid(ur->uid)) |
238 | return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, | |
239 | "Acquired user record does not have a UID."); | |
8c6db833 | 240 | |
9ab0d3eb LP |
241 | if (ret_record) |
242 | *ret_record = TAKE_PTR(ur); | |
8c6db833 LP |
243 | |
244 | return PAM_SUCCESS; | |
245 | } | |
246 | ||
ecd5f1a9 LP |
247 | static bool display_is_local(const char *display) { |
248 | assert(display); | |
249 | ||
250 | return | |
251 | display[0] == ':' && | |
ff25d338 | 252 | ascii_isdigit(display[1]); |
ecd5f1a9 LP |
253 | } |
254 | ||
ddf127cd TM |
255 | static int socket_from_display(const char *display) { |
256 | _cleanup_free_ char *f = NULL; | |
f7b8b5c4 | 257 | size_t k; |
ddf127cd TM |
258 | char *c; |
259 | union sockaddr_union sa; | |
260 | socklen_t sa_len; | |
254d1313 | 261 | _cleanup_close_ int fd = -EBADF; |
ddf127cd | 262 | int r; |
f7b8b5c4 LP |
263 | |
264 | assert(display); | |
f7b8b5c4 LP |
265 | |
266 | if (!display_is_local(display)) | |
267 | return -EINVAL; | |
268 | ||
269 | k = strspn(display+1, "0123456789"); | |
270 | ||
ddf127cd TM |
271 | /* Try abstract socket first. */ |
272 | f = new(char, STRLEN("@/tmp/.X11-unix/X") + k + 1); | |
f7b8b5c4 LP |
273 | if (!f) |
274 | return -ENOMEM; | |
275 | ||
ddf127cd | 276 | c = stpcpy(f, "@/tmp/.X11-unix/X"); |
f7b8b5c4 LP |
277 | memcpy(c, display+1, k); |
278 | c[k] = 0; | |
279 | ||
ddf127cd TM |
280 | r = sockaddr_un_set_path(&sa.un, f); |
281 | if (r < 0) | |
282 | return r; | |
283 | sa_len = r; | |
f7b8b5c4 | 284 | |
ddf127cd TM |
285 | fd = RET_NERRNO(socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0)); |
286 | if (fd < 0) | |
287 | return fd; | |
288 | ||
289 | r = RET_NERRNO(connect(fd, &sa.sa, sa_len)); | |
290 | if (r >= 0) | |
291 | return TAKE_FD(fd); | |
292 | if (r != -ECONNREFUSED) | |
293 | return r; | |
294 | ||
295 | /* Try also non-abstract socket. */ | |
296 | r = sockaddr_un_set_path(&sa.un, f + 1); | |
297 | if (r < 0) | |
298 | return r; | |
299 | sa_len = r; | |
300 | ||
301 | r = RET_NERRNO(connect(fd, &sa.sa, sa_len)); | |
302 | if (r >= 0) | |
303 | return TAKE_FD(fd); | |
304 | return r; | |
f7b8b5c4 LP |
305 | } |
306 | ||
4d6d6518 | 307 | static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) { |
ddf127cd | 308 | _cleanup_free_ char *sys_path = NULL, *tty = NULL; |
254d1313 | 309 | _cleanup_close_ int fd = -EBADF; |
4d6d6518 | 310 | struct ucred ucred; |
f36a9d59 | 311 | int v, r; |
e21d9060 | 312 | dev_t display_ctty; |
4d6d6518 LP |
313 | |
314 | assert(display); | |
4d6d6518 LP |
315 | assert(vtnr); |
316 | ||
317 | /* We deduce the X11 socket from the display name, then use | |
318 | * SO_PEERCRED to determine the X11 server process, ask for | |
319 | * the controlling tty of that and if it's a VC then we know | |
320 | * the seat and the virtual terminal. Sounds ugly, is only | |
321 | * semi-ugly. */ | |
322 | ||
ddf127cd | 323 | fd = socket_from_display(display); |
b92bea5d | 324 | if (fd < 0) |
ddf127cd | 325 | return fd; |
4d6d6518 | 326 | |
eff05270 | 327 | r = getpeercred(fd, &ucred); |
4d6d6518 | 328 | if (r < 0) |
eff05270 | 329 | return r; |
4d6d6518 | 330 | |
e21d9060 TM |
331 | r = get_ctty_devnr(ucred.pid, &display_ctty); |
332 | if (r < 0) | |
333 | return r; | |
334 | ||
3a4e4ffa | 335 | if (asprintf(&sys_path, "/sys/dev/char/" DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(display_ctty)) < 0) |
e21d9060 TM |
336 | return -ENOMEM; |
337 | r = readlink_value(sys_path, &tty); | |
4d6d6518 LP |
338 | if (r < 0) |
339 | return r; | |
340 | ||
341 | v = vtnr_from_tty(tty); | |
4d6d6518 LP |
342 | if (v < 0) |
343 | return v; | |
344 | else if (v == 0) | |
345 | return -ENOENT; | |
346 | ||
fc7985ed LP |
347 | if (seat) |
348 | *seat = "seat0"; | |
4d6d6518 LP |
349 | *vtnr = (uint32_t) v; |
350 | ||
351 | return 0; | |
352 | } | |
353 | ||
00efd498 ZJS |
354 | static int export_legacy_dbus_address( |
355 | pam_handle_t *handle, | |
00efd498 ZJS |
356 | const char *runtime) { |
357 | ||
15ee6c20 ZJS |
358 | const char *s; |
359 | _cleanup_free_ char *t = NULL; | |
00efd498 ZJS |
360 | int r = PAM_BUF_ERR; |
361 | ||
15ee6c20 ZJS |
362 | /* We need to export $DBUS_SESSION_BUS_ADDRESS because various applications will not connect |
363 | * correctly to the bus without it. This setting matches what dbus.socket does for the user | |
364 | * session using 'systemctl --user set-environment'. We want to have the same configuration | |
365 | * in processes started from the PAM session. | |
366 | * | |
367 | * The setting of the address is guarded by the access() check because it is also possible to compile | |
368 | * dbus without --enable-user-session, in which case this socket is not used, and | |
369 | * $DBUS_SESSION_BUS_ADDRESS should not be set. An alternative approach would to not do the access() | |
370 | * check here, and let applications try on their own, by using "unix:path=%s/bus;autolaunch:". But we | |
371 | * expect the socket to be present by the time we do this check, so we can just as well check once | |
372 | * here. */ | |
373 | ||
374 | s = strjoina(runtime, "/bus"); | |
375 | if (access(s, F_OK) < 0) | |
376 | return PAM_SUCCESS; | |
377 | ||
378 | if (asprintf(&t, DEFAULT_USER_BUS_ADDRESS_FMT, runtime) < 0) | |
d750dde2 | 379 | return pam_log_oom(handle); |
00efd498 | 380 | |
15ee6c20 | 381 | r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", t, 0); |
f48e9376 ZJS |
382 | if (r != PAM_SUCCESS) |
383 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set bus variable: @PAMERR@"); | |
00efd498 ZJS |
384 | |
385 | return PAM_SUCCESS; | |
00efd498 ZJS |
386 | } |
387 | ||
22f93314 JS |
388 | static int append_session_memory_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) { |
389 | uint64_t val; | |
390 | int r; | |
391 | ||
392 | if (isempty(limit)) | |
7bfbf6cc | 393 | return PAM_SUCCESS; |
22f93314 JS |
394 | |
395 | if (streq(limit, "infinity")) { | |
f5fbe71d | 396 | r = sd_bus_message_append(m, "(sv)", "MemoryMax", "t", UINT64_MAX); |
d750dde2 LP |
397 | if (r < 0) |
398 | return pam_bus_log_create_error(handle, r); | |
399 | ||
7bfbf6cc | 400 | return PAM_SUCCESS; |
d750dde2 LP |
401 | } |
402 | ||
fe845b5e | 403 | r = parse_permyriad(limit); |
d750dde2 | 404 | if (r >= 0) { |
9cba32bc | 405 | r = sd_bus_message_append(m, "(sv)", "MemoryMaxScale", "u", UINT32_SCALE_FROM_PERMYRIAD(r)); |
d750dde2 LP |
406 | if (r < 0) |
407 | return pam_bus_log_create_error(handle, r); | |
408 | ||
7bfbf6cc | 409 | return PAM_SUCCESS; |
d750dde2 LP |
410 | } |
411 | ||
412 | r = parse_size(limit, 1024, &val); | |
413 | if (r >= 0) { | |
414 | r = sd_bus_message_append(m, "(sv)", "MemoryMax", "t", val); | |
415 | if (r < 0) | |
416 | return pam_bus_log_create_error(handle, r); | |
417 | ||
7bfbf6cc | 418 | return PAM_SUCCESS; |
22f93314 JS |
419 | } |
420 | ||
d750dde2 | 421 | pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.memory_max, ignoring: %s", limit); |
7bfbf6cc | 422 | return PAM_SUCCESS; |
22f93314 JS |
423 | } |
424 | ||
adc09af2 PW |
425 | static int append_session_runtime_max_sec(pam_handle_t *handle, sd_bus_message *m, const char *limit) { |
426 | usec_t val; | |
427 | int r; | |
428 | ||
429 | /* No need to parse "infinity" here, it will be set by default later in scope_init() */ | |
430 | if (isempty(limit) || streq(limit, "infinity")) | |
7bfbf6cc | 431 | return PAM_SUCCESS; |
adc09af2 PW |
432 | |
433 | r = parse_sec(limit, &val); | |
434 | if (r >= 0) { | |
435 | r = sd_bus_message_append(m, "(sv)", "RuntimeMaxUSec", "t", (uint64_t) val); | |
7bfbf6cc LP |
436 | if (r < 0) |
437 | return pam_bus_log_create_error(handle, r); | |
adc09af2 PW |
438 | } else |
439 | pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.runtime_max_sec: %s, ignoring.", limit); | |
440 | ||
7bfbf6cc | 441 | return PAM_SUCCESS; |
adc09af2 PW |
442 | } |
443 | ||
5fdfbbd5 | 444 | static int append_session_tasks_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) { |
22f93314 JS |
445 | uint64_t val; |
446 | int r; | |
447 | ||
448 | /* No need to parse "infinity" here, it will be set unconditionally later in manager_start_scope() */ | |
449 | if (isempty(limit) || streq(limit, "infinity")) | |
7bfbf6cc | 450 | return PAM_SUCCESS; |
22f93314 JS |
451 | |
452 | r = safe_atou64(limit, &val); | |
453 | if (r >= 0) { | |
454 | r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", val); | |
d750dde2 LP |
455 | if (r < 0) |
456 | return pam_bus_log_create_error(handle, r); | |
22f93314 | 457 | } else |
d750dde2 | 458 | pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.tasks_max, ignoring: %s", limit); |
22f93314 | 459 | |
7bfbf6cc | 460 | return PAM_SUCCESS; |
22f93314 JS |
461 | } |
462 | ||
e7906abe | 463 | static int append_session_cpu_weight(pam_handle_t *handle, sd_bus_message *m, const char *limit) { |
22f93314 JS |
464 | uint64_t val; |
465 | int r; | |
466 | ||
36a4dbae | 467 | if (isempty(limit)) |
7bfbf6cc | 468 | return PAM_SUCCESS; |
36a4dbae | 469 | |
e7906abe LP |
470 | r = cg_cpu_weight_parse(limit, &val); |
471 | if (r < 0) | |
472 | pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.cpu_weight, ignoring: %s", limit); | |
473 | else { | |
474 | r = sd_bus_message_append(m, "(sv)", "CPUWeight", "t", val); | |
d750dde2 LP |
475 | if (r < 0) |
476 | return pam_bus_log_create_error(handle, r); | |
e7906abe LP |
477 | } |
478 | ||
479 | return PAM_SUCCESS; | |
480 | } | |
481 | ||
482 | static int append_session_io_weight(pam_handle_t *handle, sd_bus_message *m, const char *limit) { | |
483 | uint64_t val; | |
484 | int r; | |
485 | ||
486 | if (isempty(limit)) | |
487 | return PAM_SUCCESS; | |
488 | ||
489 | r = cg_weight_parse(limit, &val); | |
490 | if (r < 0) | |
d750dde2 | 491 | pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.io_weight, ignoring: %s", limit); |
e7906abe LP |
492 | else { |
493 | r = sd_bus_message_append(m, "(sv)", "IOWeight", "t", val); | |
494 | if (r < 0) | |
495 | return pam_bus_log_create_error(handle, r); | |
496 | } | |
22f93314 | 497 | |
7bfbf6cc | 498 | return PAM_SUCCESS; |
22f93314 JS |
499 | } |
500 | ||
0ecc1c9d LP |
501 | static const char* getenv_harder(pam_handle_t *handle, const char *key, const char *fallback) { |
502 | const char *v; | |
503 | ||
504 | assert(handle); | |
505 | assert(key); | |
506 | ||
5238e957 | 507 | /* Looks for an environment variable, preferably in the environment block associated with the |
83d4ab55 LP |
508 | * specified PAM handle, falling back to the process' block instead. Why check both? Because we want |
509 | * to permit configuration of session properties from unit files that invoke PAM services, so that | |
510 | * PAM services don't have to be reworked to set systemd-specific properties, but these properties | |
511 | * can still be set from the unit file Environment= block. */ | |
0ecc1c9d LP |
512 | |
513 | v = pam_getenv(handle, key); | |
514 | if (!isempty(v)) | |
515 | return v; | |
516 | ||
83d4ab55 LP |
517 | /* We use secure_getenv() here, since we might get loaded into su/sudo, which are SUID. Ideally |
518 | * they'd clean up the environment before invoking foreign code (such as PAM modules), but alas they | |
519 | * currently don't (to be precise, they clean up the environment they pass to their children, but | |
520 | * not their own environ[]). */ | |
521 | v = secure_getenv(key); | |
0ecc1c9d LP |
522 | if (!isempty(v)) |
523 | return v; | |
524 | ||
525 | return fallback; | |
526 | } | |
527 | ||
d6baaa69 LP |
528 | static int update_environment(pam_handle_t *handle, const char *key, const char *value) { |
529 | int r; | |
530 | ||
531 | assert(handle); | |
532 | assert(key); | |
533 | ||
534 | /* Updates the environment, but only if there's actually a value set. Also, log about errors */ | |
535 | ||
536 | if (isempty(value)) | |
537 | return PAM_SUCCESS; | |
538 | ||
539 | r = pam_misc_setenv(handle, key, value, 0); | |
540 | if (r != PAM_SUCCESS) | |
f48e9376 ZJS |
541 | return pam_syslog_pam_error(handle, LOG_ERR, r, |
542 | "Failed to set environment variable %s: @PAMERR@", key); | |
d6baaa69 | 543 | |
f48e9376 | 544 | return PAM_SUCCESS; |
d6baaa69 LP |
545 | } |
546 | ||
b9217112 LP |
547 | static bool validate_runtime_directory(pam_handle_t *handle, const char *path, uid_t uid) { |
548 | struct stat st; | |
549 | ||
dca81e28 | 550 | assert(handle); |
b9217112 LP |
551 | assert(path); |
552 | ||
e0d70f76 LP |
553 | /* Some extra paranoia: let's not set $XDG_RUNTIME_DIR if the directory we'd set it to isn't actually |
554 | * set up properly for us. This is supposed to provide a careful safety net for supporting su/sudo | |
555 | * type transitions: in that case the UID changes, but the session and thus the user owning it | |
15e6a6e8 | 556 | * doesn't change. Since the $XDG_RUNTIME_DIR lifecycle is bound to the session's user being logged |
e0d70f76 LP |
557 | * in at least once we should be particularly careful when setting the environment variable, since |
558 | * otherwise we might end up setting $XDG_RUNTIME_DIR to some directory owned by the wrong user. */ | |
b9217112 | 559 | |
6d06dfad LP |
560 | if (!path_is_absolute(path)) { |
561 | pam_syslog(handle, LOG_ERR, "Provided runtime directory '%s' is not absolute.", path); | |
562 | goto fail; | |
563 | } | |
564 | ||
b9217112 | 565 | if (lstat(path, &st) < 0) { |
7e7b53b4 | 566 | pam_syslog_errno(handle, LOG_ERR, errno, "Failed to stat() runtime directory '%s': %m", path); |
b9217112 LP |
567 | goto fail; |
568 | } | |
569 | ||
570 | if (!S_ISDIR(st.st_mode)) { | |
571 | pam_syslog(handle, LOG_ERR, "Runtime directory '%s' is not actually a directory.", path); | |
572 | goto fail; | |
573 | } | |
574 | ||
575 | if (st.st_uid != uid) { | |
576 | pam_syslog(handle, LOG_ERR, "Runtime directory '%s' is not owned by UID " UID_FMT ", as it should.", path, uid); | |
577 | goto fail; | |
578 | } | |
579 | ||
580 | return true; | |
581 | ||
582 | fail: | |
583 | pam_syslog(handle, LOG_WARNING, "Not setting $XDG_RUNTIME_DIR, as the directory is not in order."); | |
584 | return false; | |
585 | } | |
586 | ||
f9c1f4e1 LP |
587 | static int pam_putenv_and_log(pam_handle_t *handle, const char *e, bool debug) { |
588 | int r; | |
589 | ||
590 | assert(handle); | |
591 | assert(e); | |
592 | ||
593 | r = pam_putenv(handle, e); | |
f48e9376 ZJS |
594 | if (r != PAM_SUCCESS) |
595 | return pam_syslog_pam_error(handle, LOG_ERR, r, | |
596 | "Failed to set PAM environment variable %s: @PAMERR@", e); | |
f9c1f4e1 | 597 | |
27ccba26 | 598 | pam_debug_syslog(handle, debug, "PAM environment variable %s set based on user record.", e); |
f9c1f4e1 LP |
599 | |
600 | return PAM_SUCCESS; | |
601 | } | |
602 | ||
bf1b9ae4 LP |
603 | static int apply_user_record_settings( |
604 | pam_handle_t *handle, | |
605 | UserRecord *ur, | |
606 | bool debug, | |
607 | uint64_t default_capability_bounding_set, | |
608 | uint64_t default_capability_ambient_set) { | |
f9c1f4e1 LP |
609 | int r; |
610 | ||
611 | assert(handle); | |
612 | assert(ur); | |
613 | ||
614 | if (ur->umask != MODE_INVALID) { | |
615 | umask(ur->umask); | |
27ccba26 | 616 | pam_debug_syslog(handle, debug, "Set user umask to %04o based on user record.", ur->umask); |
f9c1f4e1 LP |
617 | } |
618 | ||
619 | STRV_FOREACH(i, ur->environment) { | |
620 | _cleanup_free_ char *n = NULL; | |
621 | const char *e; | |
622 | ||
623 | assert_se(e = strchr(*i, '=')); /* environment was already validated while parsing JSON record, this thus must hold */ | |
624 | ||
625 | n = strndup(*i, e - *i); | |
626 | if (!n) | |
627 | return pam_log_oom(handle); | |
628 | ||
629 | if (pam_getenv(handle, n)) { | |
27ccba26 ZJS |
630 | pam_debug_syslog(handle, debug, |
631 | "PAM environment variable $%s already set, not changing based on record.", *i); | |
f9c1f4e1 LP |
632 | continue; |
633 | } | |
634 | ||
635 | r = pam_putenv_and_log(handle, *i, debug); | |
636 | if (r != PAM_SUCCESS) | |
637 | return r; | |
638 | } | |
639 | ||
640 | if (ur->email_address) { | |
27ccba26 ZJS |
641 | if (pam_getenv(handle, "EMAIL")) |
642 | pam_debug_syslog(handle, debug, | |
643 | "PAM environment variable $EMAIL already set, not changing based on user record."); | |
644 | else { | |
f9c1f4e1 LP |
645 | _cleanup_free_ char *joined = NULL; |
646 | ||
647 | joined = strjoin("EMAIL=", ur->email_address); | |
648 | if (!joined) | |
649 | return pam_log_oom(handle); | |
650 | ||
651 | r = pam_putenv_and_log(handle, joined, debug); | |
652 | if (r != PAM_SUCCESS) | |
653 | return r; | |
654 | } | |
655 | } | |
656 | ||
657 | if (ur->time_zone) { | |
27ccba26 ZJS |
658 | if (pam_getenv(handle, "TZ")) |
659 | pam_debug_syslog(handle, debug, | |
660 | "PAM environment variable $TZ already set, not changing based on user record."); | |
661 | else if (!timezone_is_valid(ur->time_zone, LOG_DEBUG)) | |
662 | pam_debug_syslog(handle, debug, | |
663 | "Time zone specified in user record is not valid locally, not setting $TZ."); | |
664 | else { | |
f9c1f4e1 LP |
665 | _cleanup_free_ char *joined = NULL; |
666 | ||
667 | joined = strjoin("TZ=:", ur->time_zone); | |
668 | if (!joined) | |
669 | return pam_log_oom(handle); | |
670 | ||
671 | r = pam_putenv_and_log(handle, joined, debug); | |
672 | if (r != PAM_SUCCESS) | |
673 | return r; | |
674 | } | |
675 | } | |
676 | ||
677 | if (ur->preferred_language) { | |
27ccba26 ZJS |
678 | if (pam_getenv(handle, "LANG")) |
679 | pam_debug_syslog(handle, debug, | |
680 | "PAM environment variable $LANG already set, not changing based on user record."); | |
681 | else if (locale_is_installed(ur->preferred_language) <= 0) | |
682 | pam_debug_syslog(handle, debug, | |
683 | "Preferred language specified in user record is not valid or not installed, not setting $LANG."); | |
684 | else { | |
f9c1f4e1 LP |
685 | _cleanup_free_ char *joined = NULL; |
686 | ||
687 | joined = strjoin("LANG=", ur->preferred_language); | |
688 | if (!joined) | |
689 | return pam_log_oom(handle); | |
690 | ||
691 | r = pam_putenv_and_log(handle, joined, debug); | |
692 | if (r != PAM_SUCCESS) | |
693 | return r; | |
694 | } | |
695 | } | |
696 | ||
697 | if (nice_is_valid(ur->nice_level)) { | |
698 | if (nice(ur->nice_level) < 0) | |
7e7b53b4 ZJS |
699 | pam_syslog_errno(handle, LOG_ERR, errno, |
700 | "Failed to set nice level to %i, ignoring: %m", ur->nice_level); | |
27ccba26 ZJS |
701 | else |
702 | pam_debug_syslog(handle, debug, | |
703 | "Nice level set to %i, based on user record.", ur->nice_level); | |
f9c1f4e1 LP |
704 | } |
705 | ||
706 | for (int rl = 0; rl < _RLIMIT_MAX; rl++) { | |
707 | ||
708 | if (!ur->rlimits[rl]) | |
709 | continue; | |
710 | ||
711 | r = setrlimit_closest(rl, ur->rlimits[rl]); | |
712 | if (r < 0) | |
7e7b53b4 ZJS |
713 | pam_syslog_errno(handle, LOG_ERR, r, |
714 | "Failed to set resource limit %s, ignoring: %m", rlimit_to_string(rl)); | |
27ccba26 ZJS |
715 | else |
716 | pam_debug_syslog(handle, debug, | |
717 | "Resource limit %s set, based on user record.", rlimit_to_string(rl)); | |
f9c1f4e1 LP |
718 | } |
719 | ||
bf1b9ae4 LP |
720 | uint64_t a, b; |
721 | a = user_record_capability_ambient_set(ur); | |
722 | if (a == UINT64_MAX) | |
723 | a = default_capability_ambient_set; | |
724 | ||
725 | b = user_record_capability_bounding_set(ur); | |
726 | if (b == UINT64_MAX) | |
727 | b = default_capability_bounding_set; | |
728 | ||
729 | if (a != UINT64_MAX && a != 0) { | |
730 | a &= b; | |
731 | ||
732 | r = capability_ambient_set_apply(a, /* also_inherit= */ true); | |
733 | if (r < 0) | |
734 | pam_syslog_errno(handle, LOG_ERR, r, | |
735 | "Failed to set ambient capabilities, ignoring: %m"); | |
736 | } | |
737 | ||
738 | if (b != UINT64_MAX && !cap_test_all(b)) { | |
739 | r = capability_bounding_set_drop(b, /* right_now= */ false); | |
740 | if (r < 0) | |
741 | pam_syslog_errno(handle, LOG_ERR, r, | |
742 | "Failed to set bounding capabilities, ignoring: %m"); | |
743 | } | |
744 | ||
f9c1f4e1 LP |
745 | return PAM_SUCCESS; |
746 | } | |
747 | ||
e0d70f76 LP |
748 | static int configure_runtime_directory( |
749 | pam_handle_t *handle, | |
750 | UserRecord *ur, | |
751 | const char *rt) { | |
752 | ||
753 | int r; | |
754 | ||
755 | assert(handle); | |
756 | assert(ur); | |
757 | assert(rt); | |
758 | ||
759 | if (!validate_runtime_directory(handle, rt, ur->uid)) | |
760 | return PAM_SUCCESS; | |
761 | ||
762 | r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0); | |
f48e9376 ZJS |
763 | if (r != PAM_SUCCESS) |
764 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set runtime dir: @PAMERR@"); | |
e0d70f76 LP |
765 | |
766 | return export_legacy_dbus_address(handle, rt); | |
767 | } | |
768 | ||
bf1b9ae4 LP |
769 | static uint64_t pick_default_capability_ambient_set( |
770 | UserRecord *ur, | |
771 | const char *service, | |
772 | const char *seat) { | |
773 | ||
774 | /* If not configured otherwise, let's enable CAP_WAKE_ALARM for regular users when logging in on a | |
775 | * seat (i.e. when they are present physically on the device), or when invoked for the systemd --user | |
776 | * instances. This allows desktops to install CAP_WAKE_ALARM to implement alarm clock apps without | |
777 | * much fuss. */ | |
778 | ||
779 | return ur && | |
780 | user_record_disposition(ur) == USER_REGULAR && | |
781 | (streq_ptr(service, "systemd-user") || !isempty(seat)) ? (UINT64_C(1) << CAP_WAKE_ALARM) : UINT64_MAX; | |
782 | } | |
783 | ||
76f2191d MS |
784 | typedef struct SessionContext { |
785 | const uid_t uid; | |
786 | const pid_t pid; | |
787 | const char *service; | |
788 | const char *type; | |
789 | const char *class; | |
790 | const char *desktop; | |
791 | const char *seat; | |
792 | const uint32_t vtnr; | |
793 | const char *tty; | |
794 | const char *display; | |
795 | const bool remote; | |
796 | const char *remote_user; | |
797 | const char *remote_host; | |
798 | const char *memory_max; | |
799 | const char *tasks_max; | |
800 | const char *cpu_weight; | |
801 | const char *io_weight; | |
802 | const char *runtime_max_sec; | |
803 | } SessionContext; | |
804 | ||
79f36b64 MY |
805 | static int create_session_message( |
806 | sd_bus *bus, | |
807 | pam_handle_t *handle, | |
808 | const SessionContext *context, | |
809 | bool avoid_pidfd, | |
810 | sd_bus_message **ret) { | |
811 | ||
76f2191d | 812 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; |
79f36b64 MY |
813 | _cleanup_close_ int pidfd = -EBADF; |
814 | int r; | |
76f2191d MS |
815 | |
816 | assert(bus); | |
817 | assert(handle); | |
818 | assert(context); | |
79f36b64 | 819 | assert(ret); |
76f2191d MS |
820 | |
821 | if (!avoid_pidfd) { | |
822 | pidfd = pidfd_open(getpid_cached(), 0); | |
823 | if (pidfd < 0 && !ERRNO_IS_NOT_SUPPORTED(errno)) | |
824 | return -errno; | |
825 | } | |
826 | ||
827 | r = bus_message_new_method_call(bus, &m, bus_login_mgr, pidfd >= 0 ? "CreateSessionWithPIDFD" : "CreateSession"); | |
828 | if (r < 0) | |
829 | return r; | |
830 | ||
831 | r = sd_bus_message_append(m, | |
832 | pidfd >= 0 ? "uhsssssussbss" : "uusssssussbss", | |
833 | (uint32_t) context->uid, | |
834 | pidfd >= 0 ? pidfd : context->pid, | |
835 | context->service, | |
836 | context->type, | |
837 | context->class, | |
838 | context->desktop, | |
839 | context->seat, | |
840 | context->vtnr, | |
841 | context->tty, | |
842 | context->display, | |
843 | context->remote, | |
844 | context->remote_user, | |
845 | context->remote_host); | |
846 | if (r < 0) | |
847 | return r; | |
848 | ||
849 | if (pidfd >= 0) { | |
850 | r = sd_bus_message_append(m, "t", UINT64_C(0)); | |
851 | if (r < 0) | |
852 | return r; | |
853 | } | |
854 | ||
855 | r = sd_bus_message_open_container(m, 'a', "(sv)"); | |
856 | if (r < 0) | |
857 | return r; | |
858 | ||
859 | r = append_session_memory_max(handle, m, context->memory_max); | |
860 | if (r != PAM_SUCCESS) | |
861 | return r; | |
862 | ||
863 | r = append_session_runtime_max_sec(handle, m, context->runtime_max_sec); | |
864 | if (r != PAM_SUCCESS) | |
865 | return r; | |
866 | ||
867 | r = append_session_tasks_max(handle, m, context->tasks_max); | |
868 | if (r != PAM_SUCCESS) | |
869 | return r; | |
870 | ||
871 | r = append_session_cpu_weight(handle, m, context->cpu_weight); | |
872 | if (r != PAM_SUCCESS) | |
873 | return r; | |
874 | ||
875 | r = append_session_io_weight(handle, m, context->io_weight); | |
876 | if (r != PAM_SUCCESS) | |
877 | return r; | |
878 | ||
879 | r = sd_bus_message_close_container(m); | |
880 | if (r < 0) | |
881 | return r; | |
882 | ||
883 | *ret = TAKE_PTR(m); | |
884 | return 0; | |
885 | } | |
886 | ||
98a28fef | 887 | _public_ PAM_EXTERN int pam_sm_open_session( |
8c6db833 LP |
888 | pam_handle_t *handle, |
889 | int flags, | |
890 | int argc, const char **argv) { | |
891 | ||
ba8d00e8 LP |
892 | /* Let's release the D-Bus connection once this function exits, after all the session might live |
893 | * quite a long time, and we are not going to process the bus connection in that time, so let's | |
894 | * better close before the daemon kicks us off because we are not processing anything. */ | |
895 | _cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL; | |
4afd3348 | 896 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
22f93314 | 897 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; |
d1529c9e | 898 | const char |
9ab0d3eb | 899 | *id, *object_path, *runtime_path, |
d1529c9e LP |
900 | *service = NULL, |
901 | *tty = NULL, *display = NULL, | |
902 | *remote_user = NULL, *remote_host = NULL, | |
903 | *seat = NULL, | |
904 | *type = NULL, *class = NULL, | |
f5cb2820 | 905 | *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL, *desktop_pam = NULL, |
adc09af2 | 906 | *memory_max = NULL, *tasks_max = NULL, *cpu_weight = NULL, *io_weight = NULL, *runtime_max_sec = NULL; |
bf1b9ae4 | 907 | uint64_t default_capability_bounding_set = UINT64_MAX, default_capability_ambient_set = UINT64_MAX; |
4afd3348 | 908 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; |
9ab0d3eb | 909 | _cleanup_(user_record_unrefp) UserRecord *ur = NULL; |
254d1313 | 910 | int session_fd = -EBADF, existing, r; |
d1529c9e | 911 | bool debug = false, remote; |
baae0358 LP |
912 | uint32_t vtnr = 0; |
913 | uid_t original_uid; | |
8c6db833 | 914 | |
ffcfcb6b | 915 | assert(handle); |
98a28fef | 916 | |
e9fbc77c LP |
917 | if (parse_argv(handle, |
918 | argc, argv, | |
fb6becb4 | 919 | &class_pam, |
49ebd11f | 920 | &type_pam, |
f5cb2820 | 921 | &desktop_pam, |
bf1b9ae4 LP |
922 | &debug, |
923 | &default_capability_bounding_set, | |
924 | &default_capability_ambient_set) < 0) | |
5a330cda | 925 | return PAM_SESSION_ERR; |
74fe1fe3 | 926 | |
27ccba26 | 927 | pam_debug_syslog(handle, debug, "pam-systemd initializing"); |
baae0358 | 928 | |
9ab0d3eb | 929 | r = acquire_user_record(handle, &ur); |
42e66809 | 930 | if (r != PAM_SUCCESS) |
5a330cda | 931 | return r; |
8c6db833 | 932 | |
e945dd9e LP |
933 | /* Make most of this a NOP on non-logind systems */ |
934 | if (!logind_running()) | |
935 | goto success; | |
936 | ||
c0cb9e4a LP |
937 | r = pam_get_item_many( |
938 | handle, | |
939 | PAM_SERVICE, &service, | |
940 | PAM_XDISPLAY, &display, | |
941 | PAM_TTY, &tty, | |
942 | PAM_RUSER, &remote_user, | |
943 | PAM_RHOST, &remote_host); | |
944 | if (r != PAM_SUCCESS) | |
945 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM items: @PAMERR@"); | |
946 | ||
0ecc1c9d LP |
947 | seat = getenv_harder(handle, "XDG_SEAT", NULL); |
948 | cvtnr = getenv_harder(handle, "XDG_VTNR", NULL); | |
949 | type = getenv_harder(handle, "XDG_SESSION_TYPE", type_pam); | |
950 | class = getenv_harder(handle, "XDG_SESSION_CLASS", class_pam); | |
f5cb2820 | 951 | desktop = getenv_harder(handle, "XDG_SESSION_DESKTOP", desktop_pam); |
a4cd87e9 | 952 | |
4cb4e6cf LP |
953 | if (streq_ptr(service, "systemd-user")) { |
954 | /* If we detect that we are running in the "systemd-user" PAM stack, then let's patch the class to | |
955 | * 'manager' if not set, simply for robustness reasons. */ | |
956 | type = "unspecified"; | |
957 | class = IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) ? | |
958 | "manager-early" : "manager"; | |
959 | tty = NULL; | |
960 | ||
961 | } else if (tty && strchr(tty, ':')) { | |
3a736949 LP |
962 | /* A tty with a colon is usually an X11 display, placed there to show up in utmp. We rearrange things |
963 | * and don't pretend that an X display was a tty. */ | |
ee8545b0 LP |
964 | if (isempty(display)) |
965 | display = tty; | |
a4cd87e9 | 966 | tty = NULL; |
3a736949 | 967 | |
3d010bc5 | 968 | } else if (streq_ptr(tty, "cron")) { |
3a736949 LP |
969 | /* cron is setting PAM_TTY to "cron" for some reason (the commit carries no information why, but |
970 | * probably because it wants to set it to something as pam_time/pam_access/… require PAM_TTY to be set | |
971 | * (as they otherwise even try to update it!) — but cron doesn't actually allocate a TTY for its forked | |
972 | * off processes.) */ | |
0ad1271f | 973 | type = "unspecified"; |
49ebd11f | 974 | class = "background"; |
a4cd87e9 | 975 | tty = NULL; |
3a736949 | 976 | |
3d010bc5 | 977 | } else if (streq_ptr(tty, "ssh")) { |
3a736949 LP |
978 | /* ssh has been setting PAM_TTY to "ssh" (for the same reason as cron does this, see above. For further |
979 | * details look for "PAM_TTY_KLUDGE" in the openssh sources). */ | |
3d010bc5 | 980 | type = "tty"; |
49ebd11f | 981 | class = "user"; |
3a736949 LP |
982 | tty = NULL; /* This one is particularly sad, as this means that ssh sessions — even though usually |
983 | * associated with a pty — won't be tracked by their tty in logind. This is because ssh | |
984 | * does the PAM session registration early for new connections, and registers a pty only | |
985 | * much later (this is because it doesn't know yet if it needs one at all, as whether to | |
986 | * register a pty or not is negotiated much later in the protocol). */ | |
513cf7da | 987 | |
3d010bc5 | 988 | } else if (tty) |
c9ed61e7 LP |
989 | /* Chop off leading /dev prefix that some clients specify, but others do not. */ |
990 | tty = skip_dev_prefix(tty); | |
ee8545b0 | 991 | |
8e7705e5 | 992 | /* If this fails vtnr will be 0, that's intended */ |
4d6d6518 | 993 | if (!isempty(cvtnr)) |
2ae4842b | 994 | (void) safe_atou32(cvtnr, &vtnr); |
4d6d6518 | 995 | |
92bd5ff3 | 996 | if (!isempty(display) && !vtnr) { |
fc7985ed | 997 | if (isempty(seat)) |
d487e2d6 | 998 | (void) get_seat_from_display(display, &seat, &vtnr); |
fc7985ed | 999 | else if (streq(seat, "seat0")) |
d487e2d6 | 1000 | (void) get_seat_from_display(display, NULL, &vtnr); |
fc7985ed | 1001 | } |
4d6d6518 | 1002 | |
49ebd11f | 1003 | if (seat && !streq(seat, "seat0") && vtnr != 0) { |
27ccba26 | 1004 | pam_debug_syslog(handle, debug, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat); |
d7353ef6 MM |
1005 | vtnr = 0; |
1006 | } | |
1007 | ||
49ebd11f | 1008 | if (isempty(type)) |
0ad1271f | 1009 | type = !isempty(display) ? "x11" : |
49ebd11f | 1010 | !isempty(tty) ? "tty" : "unspecified"; |
98a28fef | 1011 | |
55efac6c | 1012 | if (isempty(class)) |
59afe07c LP |
1013 | class = streq(type, "unspecified") ? "background" : |
1014 | ((IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) && | |
1015 | streq(type, "tty")) ? "user-early" : "user"); | |
55efac6c | 1016 | |
fecc80c1 | 1017 | remote = !isempty(remote_host) && !is_localhost(remote_host); |
98a28fef | 1018 | |
148369de | 1019 | r = pam_get_data(handle, "systemd.memory_max", (const void **)&memory_max); |
f48e9376 ZJS |
1020 | if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) |
1021 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.memory_max data: @PAMERR@"); | |
148369de | 1022 | r = pam_get_data(handle, "systemd.tasks_max", (const void **)&tasks_max); |
f48e9376 ZJS |
1023 | if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) |
1024 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.tasks_max data: @PAMERR@"); | |
148369de | 1025 | r = pam_get_data(handle, "systemd.cpu_weight", (const void **)&cpu_weight); |
f48e9376 ZJS |
1026 | if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) |
1027 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.cpu_weight data: @PAMERR@"); | |
148369de | 1028 | r = pam_get_data(handle, "systemd.io_weight", (const void **)&io_weight); |
f48e9376 ZJS |
1029 | if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) |
1030 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.io_weight data: @PAMERR@"); | |
148369de | 1031 | r = pam_get_data(handle, "systemd.runtime_max_sec", (const void **)&runtime_max_sec); |
f48e9376 ZJS |
1032 | if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) |
1033 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.runtime_max_sec data: @PAMERR@"); | |
22f93314 | 1034 | |
4d49b48c | 1035 | /* Talk to logind over the message bus */ |
ba8d00e8 | 1036 | r = pam_acquire_bus_connection(handle, "pam-systemd", &bus, &d); |
355c9966 LP |
1037 | if (r != PAM_SUCCESS) |
1038 | return r; | |
cc377381 | 1039 | |
27ccba26 ZJS |
1040 | pam_debug_syslog(handle, debug, |
1041 | "Asking logind to create session: " | |
1042 | "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", | |
1043 | ur->uid, getpid_cached(), | |
1044 | strempty(service), | |
1045 | type, class, strempty(desktop), | |
1046 | strempty(seat), vtnr, strempty(tty), strempty(display), | |
1047 | yes_no(remote), strempty(remote_user), strempty(remote_host)); | |
1048 | pam_debug_syslog(handle, debug, | |
1049 | "Session limits: " | |
1050 | "memory_max=%s tasks_max=%s cpu_weight=%s io_weight=%s runtime_max_sec=%s", | |
1051 | strna(memory_max), strna(tasks_max), strna(cpu_weight), strna(io_weight), strna(runtime_max_sec)); | |
22f93314 | 1052 | |
76f2191d MS |
1053 | const SessionContext context = { |
1054 | .uid = ur->uid, | |
1055 | .pid = 0, | |
1056 | .service = service, | |
1057 | .type = type, | |
1058 | .class = class, | |
1059 | .desktop = desktop, | |
1060 | .seat = seat, | |
1061 | .vtnr = vtnr, | |
1062 | .tty = tty, | |
1063 | .display = display, | |
1064 | .remote = remote, | |
1065 | .remote_user = remote_user, | |
1066 | .remote_host = remote_host, | |
1067 | .memory_max = memory_max, | |
1068 | .tasks_max = tasks_max, | |
1069 | .cpu_weight = cpu_weight, | |
1070 | .io_weight = io_weight, | |
1071 | .runtime_max_sec = runtime_max_sec, | |
1072 | }; | |
1073 | ||
1074 | r = create_session_message(bus, | |
1075 | handle, | |
1076 | &context, | |
79f36b64 | 1077 | /* avoid_pidfd = */ false, |
76f2191d | 1078 | &m); |
d750dde2 LP |
1079 | if (r < 0) |
1080 | return pam_bus_log_create_error(handle, r); | |
ce959314 | 1081 | |
fbcb6300 | 1082 | r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply); |
29488031 MY |
1083 | if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) { |
1084 | sd_bus_error_free(&error); | |
1085 | pam_debug_syslog(handle, debug, | |
1086 | "CreateSessionWithPIDFD() API is not available, retrying with CreateSession()."); | |
1087 | ||
1088 | m = sd_bus_message_unref(m); | |
1089 | r = create_session_message(bus, | |
1090 | handle, | |
1091 | &context, | |
1092 | /* avoid_pidfd = */ true, | |
1093 | &m); | |
1094 | if (r < 0) | |
1095 | return pam_bus_log_create_error(handle, r); | |
1096 | ||
1097 | r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply); | |
1098 | } | |
ffcfcb6b | 1099 | if (r < 0) { |
b80120c4 | 1100 | if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) { |
29488031 | 1101 | /* We are already in a session, don't do anything */ |
27ccba26 ZJS |
1102 | pam_debug_syslog(handle, debug, |
1103 | "Not creating session: %s", bus_error_message(&error, r)); | |
e945dd9e | 1104 | goto success; |
b80120c4 | 1105 | } |
29488031 MY |
1106 | |
1107 | pam_syslog(handle, LOG_ERR, | |
1108 | "Failed to create session: %s", bus_error_message(&error, r)); | |
1109 | return PAM_SESSION_ERR; | |
98a28fef | 1110 | } |
74fe1fe3 | 1111 | |
ffcfcb6b | 1112 | r = sd_bus_message_read(reply, |
baae0358 | 1113 | "soshusub", |
ffcfcb6b ZJS |
1114 | &id, |
1115 | &object_path, | |
1116 | &runtime_path, | |
1117 | &session_fd, | |
baae0358 | 1118 | &original_uid, |
ffcfcb6b ZJS |
1119 | &seat, |
1120 | &vtnr, | |
1121 | &existing); | |
d750dde2 LP |
1122 | if (r < 0) |
1123 | return pam_bus_log_parse_error(handle, r); | |
74fe1fe3 | 1124 | |
27ccba26 ZJS |
1125 | pam_debug_syslog(handle, debug, |
1126 | "Reply from logind: " | |
1127 | "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u", | |
1128 | id, object_path, runtime_path, session_fd, seat, vtnr, original_uid); | |
ce959314 | 1129 | |
d6baaa69 LP |
1130 | r = update_environment(handle, "XDG_SESSION_ID", id); |
1131 | if (r != PAM_SUCCESS) | |
5a330cda | 1132 | return r; |
8c6db833 | 1133 | |
9ab0d3eb | 1134 | if (original_uid == ur->uid) { |
e0d70f76 LP |
1135 | /* Don't set $XDG_RUNTIME_DIR if the user we now authenticated for does not match the |
1136 | * original user of the session. We do this in order not to result in privileged apps | |
1137 | * clobbering the runtime directory unnecessarily. */ | |
00efd498 | 1138 | |
e0d70f76 | 1139 | r = configure_runtime_directory(handle, ur, runtime_path); |
00efd498 ZJS |
1140 | if (r != PAM_SUCCESS) |
1141 | return r; | |
98a28fef | 1142 | } |
8c6db833 | 1143 | |
b2f74f07 LP |
1144 | /* Most likely we got the session/type/class from environment variables, but might have gotten the data |
1145 | * somewhere else (for example PAM module parameters). Let's now update the environment variables, so that this | |
1146 | * data is inherited into the session processes, and programs can rely on them to be initialized. */ | |
1147 | ||
1148 | r = update_environment(handle, "XDG_SESSION_TYPE", type); | |
1149 | if (r != PAM_SUCCESS) | |
1150 | return r; | |
1151 | ||
1152 | r = update_environment(handle, "XDG_SESSION_CLASS", class); | |
1153 | if (r != PAM_SUCCESS) | |
1154 | return r; | |
1155 | ||
1156 | r = update_environment(handle, "XDG_SESSION_DESKTOP", desktop); | |
1157 | if (r != PAM_SUCCESS) | |
1158 | return r; | |
1159 | ||
d6baaa69 LP |
1160 | r = update_environment(handle, "XDG_SEAT", seat); |
1161 | if (r != PAM_SUCCESS) | |
1162 | return r; | |
bbc73283 LP |
1163 | |
1164 | if (vtnr > 0) { | |
29d230f6 | 1165 | char buf[DECIMAL_STR_MAX(vtnr)]; |
baae0358 | 1166 | sprintf(buf, "%u", vtnr); |
bbc73283 | 1167 | |
d6baaa69 LP |
1168 | r = update_environment(handle, "XDG_VTNR", buf); |
1169 | if (r != PAM_SUCCESS) | |
5a330cda | 1170 | return r; |
bbc73283 LP |
1171 | } |
1172 | ||
77085881 | 1173 | r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL); |
f48e9376 ZJS |
1174 | if (r != PAM_SUCCESS) |
1175 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to install existing flag: @PAMERR@"); | |
77085881 | 1176 | |
21c390cc | 1177 | if (session_fd >= 0) { |
f48e9376 ZJS |
1178 | _cleanup_close_ int fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3); |
1179 | if (fd < 0) | |
7e7b53b4 | 1180 | return pam_syslog_errno(handle, LOG_ERR, errno, "Failed to dup session fd: %m"); |
5a330cda | 1181 | |
f48e9376 ZJS |
1182 | r = pam_set_data(handle, "systemd.session-fd", FD_TO_PTR(fd), NULL); |
1183 | if (r != PAM_SUCCESS) | |
1184 | return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to install session fd: @PAMERR@"); | |
1185 | TAKE_FD(fd); | |
98a28fef | 1186 | } |
8c6db833 | 1187 | |
e945dd9e | 1188 | success: |
bf1b9ae4 LP |
1189 | if (default_capability_ambient_set == UINT64_MAX) |
1190 | default_capability_ambient_set = pick_default_capability_ambient_set(ur, service, seat); | |
1191 | ||
ba8d00e8 | 1192 | return apply_user_record_settings(handle, ur, debug, default_capability_bounding_set, default_capability_ambient_set); |
98a28fef | 1193 | } |
8c6db833 | 1194 | |
98a28fef LP |
1195 | _public_ PAM_EXTERN int pam_sm_close_session( |
1196 | pam_handle_t *handle, | |
1197 | int flags, | |
1198 | int argc, const char **argv) { | |
8c6db833 | 1199 | |
5f41d1f1 | 1200 | const void *existing = NULL; |
45c5fa25 | 1201 | bool debug = false; |
75c8e3cf | 1202 | const char *id; |
75c8e3cf | 1203 | int r; |
8c6db833 | 1204 | |
ffcfcb6b | 1205 | assert(handle); |
75c8e3cf | 1206 | |
45c5fa25 LP |
1207 | if (parse_argv(handle, |
1208 | argc, argv, | |
1209 | NULL, | |
1210 | NULL, | |
1211 | NULL, | |
bf1b9ae4 LP |
1212 | &debug, |
1213 | NULL, | |
1214 | NULL) < 0) | |
45c5fa25 LP |
1215 | return PAM_SESSION_ERR; |
1216 | ||
27ccba26 | 1217 | pam_debug_syslog(handle, debug, "pam-systemd shutting down"); |
45c5fa25 | 1218 | |
77085881 LP |
1219 | /* Only release session if it wasn't pre-existing when we |
1220 | * tried to create it */ | |
148369de | 1221 | r = pam_get_data(handle, "systemd.existing", &existing); |
f48e9376 ZJS |
1222 | if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) |
1223 | return pam_syslog_pam_error(handle, LOG_ERR, r, | |
1224 | "Failed to get PAM systemd.existing data: @PAMERR@"); | |
77085881 | 1225 | |
75c8e3cf | 1226 | id = pam_getenv(handle, "XDG_SESSION_ID"); |
77085881 | 1227 | if (id && !existing) { |
355c9966 LP |
1228 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
1229 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; | |
75c8e3cf | 1230 | |
355c9966 LP |
1231 | /* Before we go and close the FIFO we need to tell logind that this is a clean session |
1232 | * shutdown, so that it doesn't just go and slaughter us immediately after closing the fd */ | |
75c8e3cf | 1233 | |
ba8d00e8 | 1234 | r = pam_acquire_bus_connection(handle, "pam-systemd", &bus, NULL); |
355c9966 LP |
1235 | if (r != PAM_SUCCESS) |
1236 | return r; | |
75c8e3cf | 1237 | |
5d990cc5 | 1238 | r = bus_call_method(bus, bus_login_mgr, "ReleaseSession", &error, NULL, "s", id); |
f48e9376 ZJS |
1239 | if (r < 0) |
1240 | return pam_syslog_pam_error(handle, LOG_ERR, PAM_SESSION_ERR, | |
1241 | "Failed to release session: %s", bus_error_message(&error, r)); | |
75c8e3cf LP |
1242 | } |
1243 | ||
355c9966 LP |
1244 | /* Note that we are knowingly leaking the FIFO fd here. This way, logind can watch us die. If we |
1245 | * closed it here it would not have any clue when that is completed. Given that one cannot really | |
1246 | * have multiple PAM sessions open from the same process this means we will leak one FD at max. */ | |
75c8e3cf | 1247 | |
5f41d1f1 | 1248 | return PAM_SUCCESS; |
8c6db833 | 1249 | } |