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