]>
Commit | Line | Data |
---|---|---|
a032b68d | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
663996b3 | 2 | |
db2df898 | 3 | #include <endian.h> |
663996b3 MS |
4 | #include <errno.h> |
5 | #include <fcntl.h> | |
663996b3 | 6 | #include <pwd.h> |
663996b3 | 7 | #include <security/_pam_macros.h> |
663996b3 MS |
8 | #include <security/pam_ext.h> |
9 | #include <security/pam_misc.h> | |
db2df898 MP |
10 | #include <security/pam_modules.h> |
11 | #include <security/pam_modutil.h> | |
12 | #include <sys/file.h> | |
bb4f798a | 13 | #include <sys/stat.h> |
46cdbd49 | 14 | #include <sys/sysmacros.h> |
bb4f798a MB |
15 | #include <sys/types.h> |
16 | #include <unistd.h> | |
663996b3 | 17 | |
db2df898 MP |
18 | #include "alloc-util.h" |
19 | #include "audit-util.h" | |
fb183854 | 20 | #include "bus-common-errors.h" |
db2df898 | 21 | #include "bus-error.h" |
bb4f798a | 22 | #include "bus-internal.h" |
a10f5d05 | 23 | #include "bus-locator.h" |
e1f67bc7 | 24 | #include "cgroup-setup.h" |
f2dec872 | 25 | #include "errno-util.h" |
db2df898 | 26 | #include "fd-util.h" |
663996b3 | 27 | #include "fileio.h" |
2897b343 | 28 | #include "format-util.h" |
46cdbd49 | 29 | #include "fs-util.h" |
e3bff60a | 30 | #include "hostname-util.h" |
46cdbd49 | 31 | #include "locale-util.h" |
db2df898 MP |
32 | #include "login-util.h" |
33 | #include "macro.h" | |
46cdbd49 | 34 | #include "pam-util.h" |
db2df898 | 35 | #include "parse-util.h" |
6e866b33 | 36 | #include "path-util.h" |
3a6ce677 | 37 | #include "percent-util.h" |
f5e65279 | 38 | #include "process-util.h" |
46cdbd49 | 39 | #include "rlimit-util.h" |
db2df898 | 40 | #include "socket-util.h" |
7c20daf6 | 41 | #include "stdio-util.h" |
db2df898 MP |
42 | #include "strv.h" |
43 | #include "terminal-util.h" | |
46cdbd49 BR |
44 | #include "user-util.h" |
45 | #include "userdb.h" | |
663996b3 | 46 | |
e1f67bc7 MB |
47 | #define LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE) |
48 | ||
60f067b4 JS |
49 | static int parse_argv( |
50 | pam_handle_t *handle, | |
51 | int argc, const char **argv, | |
52 | const char **class, | |
53 | const char **type, | |
6e866b33 | 54 | const char **desktop, |
60f067b4 | 55 | bool *debug) { |
663996b3 MS |
56 | |
57 | unsigned i; | |
58 | ||
59 | assert(argc >= 0); | |
60 | assert(argc == 0 || argv); | |
61 | ||
62 | for (i = 0; i < (unsigned) argc; i++) { | |
e1f67bc7 MB |
63 | const char *p; |
64 | ||
65 | if ((p = startswith(argv[i], "class="))) { | |
663996b3 | 66 | if (class) |
e1f67bc7 | 67 | *class = p; |
663996b3 | 68 | |
e1f67bc7 | 69 | } else if ((p = startswith(argv[i], "type="))) { |
60f067b4 | 70 | if (type) |
e1f67bc7 | 71 | *type = p; |
663996b3 | 72 | |
e1f67bc7 | 73 | } else if ((p = startswith(argv[i], "desktop="))) { |
6e866b33 | 74 | if (desktop) |
e1f67bc7 | 75 | *desktop = p; |
6e866b33 | 76 | |
60f067b4 | 77 | } else if (streq(argv[i], "debug")) { |
663996b3 | 78 | if (debug) |
60f067b4 JS |
79 | *debug = true; |
80 | ||
e1f67bc7 | 81 | } else if ((p = startswith(argv[i], "debug="))) { |
60f067b4 JS |
82 | int k; |
83 | ||
e1f67bc7 | 84 | k = parse_boolean(p); |
60f067b4 | 85 | if (k < 0) |
e1f67bc7 | 86 | pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", p); |
60f067b4 | 87 | else if (debug) |
663996b3 MS |
88 | *debug = k; |
89 | ||
60f067b4 | 90 | } else |
14228c0d | 91 | pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]); |
663996b3 MS |
92 | } |
93 | ||
94 | return 0; | |
95 | } | |
96 | ||
46cdbd49 | 97 | static int acquire_user_record( |
663996b3 | 98 | pam_handle_t *handle, |
46cdbd49 | 99 | UserRecord **ret_record) { |
663996b3 | 100 | |
46cdbd49 BR |
101 | _cleanup_(user_record_unrefp) UserRecord *ur = NULL; |
102 | const char *username = NULL, *json = NULL; | |
a10f5d05 | 103 | _cleanup_free_ char *field = NULL; |
663996b3 MS |
104 | int r; |
105 | ||
106 | assert(handle); | |
663996b3 | 107 | |
60f067b4 JS |
108 | r = pam_get_user(handle, &username, NULL); |
109 | if (r != PAM_SUCCESS) { | |
e1f67bc7 | 110 | pam_syslog(handle, LOG_ERR, "Failed to get user name: %s", pam_strerror(handle, r)); |
60f067b4 JS |
111 | return r; |
112 | } | |
663996b3 | 113 | |
60f067b4 JS |
114 | if (isempty(username)) { |
115 | pam_syslog(handle, LOG_ERR, "User name not valid."); | |
46cdbd49 | 116 | return PAM_SERVICE_ERR; |
663996b3 MS |
117 | } |
118 | ||
a10f5d05 | 119 | /* If pam_systemd_homed (or some other module) already acquired the user record we can reuse it |
46cdbd49 | 120 | * here. */ |
a10f5d05 MB |
121 | field = strjoin("systemd-user-record-", username); |
122 | if (!field) | |
123 | return pam_log_oom(handle); | |
46cdbd49 | 124 | |
a10f5d05 MB |
125 | r = pam_get_data(handle, field, (const void**) &json); |
126 | if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) { | |
127 | pam_syslog(handle, LOG_ERR, "Failed to get PAM user record data: %s", pam_strerror(handle, r)); | |
128 | return r; | |
129 | } | |
130 | if (r == PAM_SUCCESS && json) { | |
46cdbd49 BR |
131 | _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; |
132 | ||
133 | /* Parse cached record */ | |
134 | r = json_parse(json, JSON_PARSE_SENSITIVE, &v, NULL, NULL); | |
135 | if (r < 0) { | |
136 | pam_syslog(handle, LOG_ERR, "Failed to parse JSON user record: %s", strerror_safe(r)); | |
137 | return PAM_SERVICE_ERR; | |
138 | } | |
139 | ||
140 | ur = user_record_new(); | |
141 | if (!ur) | |
142 | return pam_log_oom(handle); | |
143 | ||
8b3d4ff0 | 144 | r = user_record_load(ur, v, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE); |
46cdbd49 BR |
145 | if (r < 0) { |
146 | pam_syslog(handle, LOG_ERR, "Failed to load user record: %s", strerror_safe(r)); | |
147 | return PAM_SERVICE_ERR; | |
148 | } | |
149 | ||
150 | /* Safety check if cached record actually matches what we are looking for */ | |
151 | if (!streq_ptr(username, ur->user_name)) { | |
152 | pam_syslog(handle, LOG_ERR, "Acquired user record does not match user name."); | |
153 | return PAM_SERVICE_ERR; | |
154 | } | |
a10f5d05 MB |
155 | } else { |
156 | _cleanup_free_ char *formatted = NULL; | |
157 | ||
158 | /* Request the record ourselves */ | |
159 | r = userdb_by_name(username, 0, &ur); | |
160 | if (r < 0) { | |
161 | pam_syslog(handle, LOG_ERR, "Failed to get user record: %s", strerror_safe(r)); | |
162 | return PAM_USER_UNKNOWN; | |
163 | } | |
164 | ||
165 | r = json_variant_format(ur->json, 0, &formatted); | |
166 | if (r < 0) { | |
167 | pam_syslog(handle, LOG_ERR, "Failed to format user JSON: %s", strerror_safe(r)); | |
168 | return PAM_SERVICE_ERR; | |
169 | } | |
170 | ||
171 | /* And cache it for everyone else */ | |
172 | r = pam_set_data(handle, field, formatted, pam_cleanup_free); | |
173 | if (r < 0) { | |
174 | pam_syslog(handle, LOG_ERR, "Failed to set PAM user record data '%s': %s", | |
175 | field, pam_strerror(handle, r)); | |
176 | return r; | |
177 | } | |
178 | ||
179 | TAKE_PTR(formatted); | |
46cdbd49 BR |
180 | } |
181 | ||
182 | if (!uid_is_valid(ur->uid)) { | |
183 | pam_syslog(handle, LOG_ERR, "Acquired user record does not have a UID."); | |
184 | return PAM_SERVICE_ERR; | |
663996b3 MS |
185 | } |
186 | ||
46cdbd49 BR |
187 | if (ret_record) |
188 | *ret_record = TAKE_PTR(ur); | |
663996b3 MS |
189 | |
190 | return PAM_SUCCESS; | |
191 | } | |
192 | ||
bb4f798a MB |
193 | static bool display_is_local(const char *display) { |
194 | assert(display); | |
195 | ||
196 | return | |
197 | display[0] == ':' && | |
198 | display[1] >= '0' && | |
199 | display[1] <= '9'; | |
200 | } | |
201 | ||
6e866b33 MB |
202 | static int socket_from_display(const char *display, char **path) { |
203 | size_t k; | |
204 | char *f, *c; | |
205 | ||
206 | assert(display); | |
207 | assert(path); | |
208 | ||
209 | if (!display_is_local(display)) | |
210 | return -EINVAL; | |
211 | ||
212 | k = strspn(display+1, "0123456789"); | |
213 | ||
214 | f = new(char, STRLEN("/tmp/.X11-unix/X") + k + 1); | |
215 | if (!f) | |
216 | return -ENOMEM; | |
217 | ||
218 | c = stpcpy(f, "/tmp/.X11-unix/X"); | |
219 | memcpy(c, display+1, k); | |
220 | c[k] = 0; | |
221 | ||
222 | *path = f; | |
223 | ||
224 | return 0; | |
225 | } | |
226 | ||
663996b3 | 227 | static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) { |
46cdbd49 BR |
228 | union sockaddr_union sa; |
229 | socklen_t sa_len; | |
230 | _cleanup_free_ char *p = NULL, *sys_path = NULL, *tty = NULL; | |
60f067b4 | 231 | _cleanup_close_ int fd = -1; |
663996b3 | 232 | struct ucred ucred; |
46cdbd49 BR |
233 | int v, r; |
234 | dev_t display_ctty; | |
663996b3 MS |
235 | |
236 | assert(display); | |
237 | assert(vtnr); | |
238 | ||
239 | /* We deduce the X11 socket from the display name, then use | |
240 | * SO_PEERCRED to determine the X11 server process, ask for | |
241 | * the controlling tty of that and if it's a VC then we know | |
242 | * the seat and the virtual terminal. Sounds ugly, is only | |
243 | * semi-ugly. */ | |
244 | ||
245 | r = socket_from_display(display, &p); | |
246 | if (r < 0) | |
247 | return r; | |
46cdbd49 BR |
248 | r = sockaddr_un_set_path(&sa.un, p); |
249 | if (r < 0) | |
250 | return r; | |
251 | sa_len = r; | |
663996b3 MS |
252 | |
253 | fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); | |
254 | if (fd < 0) | |
255 | return -errno; | |
256 | ||
46cdbd49 | 257 | if (connect(fd, &sa.sa, sa_len) < 0) |
663996b3 MS |
258 | return -errno; |
259 | ||
60f067b4 | 260 | r = getpeercred(fd, &ucred); |
663996b3 | 261 | if (r < 0) |
60f067b4 | 262 | return r; |
663996b3 | 263 | |
46cdbd49 BR |
264 | r = get_ctty_devnr(ucred.pid, &display_ctty); |
265 | if (r < 0) | |
266 | return r; | |
267 | ||
268 | if (asprintf(&sys_path, "/sys/dev/char/%d:%d", major(display_ctty), minor(display_ctty)) < 0) | |
269 | return -ENOMEM; | |
270 | r = readlink_value(sys_path, &tty); | |
663996b3 MS |
271 | if (r < 0) |
272 | return r; | |
273 | ||
274 | v = vtnr_from_tty(tty); | |
275 | if (v < 0) | |
276 | return v; | |
277 | else if (v == 0) | |
278 | return -ENOENT; | |
279 | ||
280 | if (seat) | |
281 | *seat = "seat0"; | |
282 | *vtnr = (uint32_t) v; | |
283 | ||
284 | return 0; | |
285 | } | |
286 | ||
7c20daf6 FS |
287 | static int export_legacy_dbus_address( |
288 | pam_handle_t *handle, | |
7c20daf6 FS |
289 | const char *runtime) { |
290 | ||
291 | const char *s; | |
292 | _cleanup_free_ char *t = NULL; | |
293 | int r = PAM_BUF_ERR; | |
294 | ||
295 | /* We need to export $DBUS_SESSION_BUS_ADDRESS because various applications will not connect | |
296 | * correctly to the bus without it. This setting matches what dbus.socket does for the user | |
297 | * session using 'systemctl --user set-environment'. We want to have the same configuration | |
298 | * in processes started from the PAM session. | |
299 | * | |
300 | * The setting of the address is guarded by the access() check because it is also possible to compile | |
301 | * dbus without --enable-user-session, in which case this socket is not used, and | |
302 | * $DBUS_SESSION_BUS_ADDRESS should not be set. An alternative approach would to not do the access() | |
303 | * check here, and let applications try on their own, by using "unix:path=%s/bus;autolaunch:". But we | |
304 | * expect the socket to be present by the time we do this check, so we can just as well check once | |
305 | * here. */ | |
306 | ||
307 | s = strjoina(runtime, "/bus"); | |
308 | if (access(s, F_OK) < 0) | |
309 | return PAM_SUCCESS; | |
310 | ||
311 | if (asprintf(&t, DEFAULT_USER_BUS_ADDRESS_FMT, runtime) < 0) | |
46cdbd49 | 312 | return pam_log_oom(handle); |
7c20daf6 FS |
313 | |
314 | r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", t, 0); | |
46cdbd49 BR |
315 | if (r != PAM_SUCCESS) { |
316 | pam_syslog(handle, LOG_ERR, "Failed to set bus variable: %s", pam_strerror(handle, r)); | |
317 | return r; | |
318 | } | |
7c20daf6 FS |
319 | |
320 | return PAM_SUCCESS; | |
7c20daf6 FS |
321 | } |
322 | ||
b012e921 MB |
323 | static int append_session_memory_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) { |
324 | uint64_t val; | |
325 | int r; | |
326 | ||
327 | if (isempty(limit)) | |
46cdbd49 | 328 | return PAM_SUCCESS; |
b012e921 MB |
329 | |
330 | if (streq(limit, "infinity")) { | |
3a6ce677 | 331 | r = sd_bus_message_append(m, "(sv)", "MemoryMax", "t", UINT64_MAX); |
46cdbd49 BR |
332 | if (r < 0) |
333 | return pam_bus_log_create_error(handle, r); | |
334 | ||
335 | return PAM_SUCCESS; | |
b012e921 MB |
336 | } |
337 | ||
3a6ce677 | 338 | r = parse_permyriad(limit); |
46cdbd49 | 339 | if (r >= 0) { |
3a6ce677 | 340 | r = sd_bus_message_append(m, "(sv)", "MemoryMaxScale", "u", UINT32_SCALE_FROM_PERMYRIAD(r)); |
46cdbd49 BR |
341 | if (r < 0) |
342 | return pam_bus_log_create_error(handle, r); | |
343 | ||
344 | return PAM_SUCCESS; | |
345 | } | |
346 | ||
347 | r = parse_size(limit, 1024, &val); | |
348 | if (r >= 0) { | |
349 | r = sd_bus_message_append(m, "(sv)", "MemoryMax", "t", val); | |
350 | if (r < 0) | |
351 | return pam_bus_log_create_error(handle, r); | |
352 | ||
353 | return PAM_SUCCESS; | |
354 | } | |
355 | ||
356 | pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.memory_max, ignoring: %s", limit); | |
357 | return PAM_SUCCESS; | |
b012e921 MB |
358 | } |
359 | ||
e1f67bc7 MB |
360 | static int append_session_runtime_max_sec(pam_handle_t *handle, sd_bus_message *m, const char *limit) { |
361 | usec_t val; | |
362 | int r; | |
363 | ||
364 | /* No need to parse "infinity" here, it will be set by default later in scope_init() */ | |
365 | if (isempty(limit) || streq(limit, "infinity")) | |
46cdbd49 | 366 | return PAM_SUCCESS; |
e1f67bc7 MB |
367 | |
368 | r = parse_sec(limit, &val); | |
369 | if (r >= 0) { | |
370 | r = sd_bus_message_append(m, "(sv)", "RuntimeMaxUSec", "t", (uint64_t) val); | |
46cdbd49 BR |
371 | if (r < 0) |
372 | return pam_bus_log_create_error(handle, r); | |
e1f67bc7 MB |
373 | } else |
374 | pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.runtime_max_sec: %s, ignoring.", limit); | |
375 | ||
46cdbd49 | 376 | return PAM_SUCCESS; |
e1f67bc7 MB |
377 | } |
378 | ||
6e866b33 | 379 | static int append_session_tasks_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) { |
b012e921 MB |
380 | uint64_t val; |
381 | int r; | |
382 | ||
383 | /* No need to parse "infinity" here, it will be set unconditionally later in manager_start_scope() */ | |
384 | if (isempty(limit) || streq(limit, "infinity")) | |
46cdbd49 | 385 | return PAM_SUCCESS; |
b012e921 MB |
386 | |
387 | r = safe_atou64(limit, &val); | |
388 | if (r >= 0) { | |
389 | r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", val); | |
46cdbd49 BR |
390 | if (r < 0) |
391 | return pam_bus_log_create_error(handle, r); | |
b012e921 | 392 | } else |
46cdbd49 | 393 | pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.tasks_max, ignoring: %s", limit); |
b012e921 | 394 | |
46cdbd49 | 395 | return PAM_SUCCESS; |
b012e921 MB |
396 | } |
397 | ||
398 | static int append_session_cg_weight(pam_handle_t *handle, sd_bus_message *m, const char *limit, const char *field) { | |
399 | uint64_t val; | |
400 | int r; | |
401 | ||
6e866b33 | 402 | if (isempty(limit)) |
46cdbd49 | 403 | return PAM_SUCCESS; |
6e866b33 MB |
404 | |
405 | r = cg_weight_parse(limit, &val); | |
406 | if (r >= 0) { | |
407 | r = sd_bus_message_append(m, "(sv)", field, "t", val); | |
46cdbd49 BR |
408 | if (r < 0) |
409 | return pam_bus_log_create_error(handle, r); | |
6e866b33 | 410 | } else if (streq(field, "CPUWeight")) |
46cdbd49 | 411 | pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.cpu_weight, ignoring: %s", limit); |
6e866b33 | 412 | else |
46cdbd49 | 413 | pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.io_weight, ignoring: %s", limit); |
b012e921 | 414 | |
46cdbd49 | 415 | return PAM_SUCCESS; |
b012e921 MB |
416 | } |
417 | ||
6e866b33 MB |
418 | static const char* getenv_harder(pam_handle_t *handle, const char *key, const char *fallback) { |
419 | const char *v; | |
420 | ||
421 | assert(handle); | |
422 | assert(key); | |
423 | ||
f2dec872 | 424 | /* Looks for an environment variable, preferably in the environment block associated with the |
bb4f798a MB |
425 | * specified PAM handle, falling back to the process' block instead. Why check both? Because we want |
426 | * to permit configuration of session properties from unit files that invoke PAM services, so that | |
427 | * PAM services don't have to be reworked to set systemd-specific properties, but these properties | |
428 | * can still be set from the unit file Environment= block. */ | |
6e866b33 MB |
429 | |
430 | v = pam_getenv(handle, key); | |
431 | if (!isempty(v)) | |
432 | return v; | |
433 | ||
bb4f798a MB |
434 | /* We use secure_getenv() here, since we might get loaded into su/sudo, which are SUID. Ideally |
435 | * they'd clean up the environment before invoking foreign code (such as PAM modules), but alas they | |
436 | * currently don't (to be precise, they clean up the environment they pass to their children, but | |
437 | * not their own environ[]). */ | |
438 | v = secure_getenv(key); | |
6e866b33 MB |
439 | if (!isempty(v)) |
440 | return v; | |
441 | ||
442 | return fallback; | |
443 | } | |
444 | ||
445 | static int update_environment(pam_handle_t *handle, const char *key, const char *value) { | |
446 | int r; | |
447 | ||
448 | assert(handle); | |
449 | assert(key); | |
450 | ||
451 | /* Updates the environment, but only if there's actually a value set. Also, log about errors */ | |
452 | ||
453 | if (isempty(value)) | |
454 | return PAM_SUCCESS; | |
455 | ||
456 | r = pam_misc_setenv(handle, key, value, 0); | |
457 | if (r != PAM_SUCCESS) | |
e1f67bc7 | 458 | pam_syslog(handle, LOG_ERR, "Failed to set environment variable %s: %s", key, pam_strerror(handle, r)); |
6e866b33 MB |
459 | |
460 | return r; | |
461 | } | |
462 | ||
463 | static bool validate_runtime_directory(pam_handle_t *handle, const char *path, uid_t uid) { | |
464 | struct stat st; | |
465 | ||
e1f67bc7 | 466 | assert(handle); |
6e866b33 MB |
467 | assert(path); |
468 | ||
a10f5d05 MB |
469 | /* Some extra paranoia: let's not set $XDG_RUNTIME_DIR if the directory we'd set it to isn't actually |
470 | * set up properly for us. This is supposed to provide a careful safety net for supporting su/sudo | |
471 | * type transitions: in that case the UID changes, but the session and thus the user owning it | |
472 | * doesn't change. Since the $XDG_RUNTIME_DIR lifecycle is bound to the session's user being logged | |
473 | * in at least once we should be particularly careful when setting the environment variable, since | |
474 | * otherwise we might end up setting $XDG_RUNTIME_DIR to some directory owned by the wrong user. */ | |
475 | ||
476 | if (!path_is_absolute(path)) { | |
477 | pam_syslog(handle, LOG_ERR, "Provided runtime directory '%s' is not absolute.", path); | |
478 | goto fail; | |
479 | } | |
6e866b33 MB |
480 | |
481 | if (lstat(path, &st) < 0) { | |
f2dec872 | 482 | pam_syslog(handle, LOG_ERR, "Failed to stat() runtime directory '%s': %s", path, strerror_safe(errno)); |
6e866b33 MB |
483 | goto fail; |
484 | } | |
485 | ||
486 | if (!S_ISDIR(st.st_mode)) { | |
487 | pam_syslog(handle, LOG_ERR, "Runtime directory '%s' is not actually a directory.", path); | |
488 | goto fail; | |
489 | } | |
490 | ||
491 | if (st.st_uid != uid) { | |
492 | pam_syslog(handle, LOG_ERR, "Runtime directory '%s' is not owned by UID " UID_FMT ", as it should.", path, uid); | |
493 | goto fail; | |
494 | } | |
495 | ||
496 | return true; | |
497 | ||
498 | fail: | |
499 | pam_syslog(handle, LOG_WARNING, "Not setting $XDG_RUNTIME_DIR, as the directory is not in order."); | |
500 | return false; | |
501 | } | |
502 | ||
46cdbd49 BR |
503 | static int pam_putenv_and_log(pam_handle_t *handle, const char *e, bool debug) { |
504 | int r; | |
505 | ||
506 | assert(handle); | |
507 | assert(e); | |
508 | ||
509 | r = pam_putenv(handle, e); | |
510 | if (r != PAM_SUCCESS) { | |
511 | pam_syslog(handle, LOG_ERR, "Failed to set PAM environment variable %s: %s", e, pam_strerror(handle, r)); | |
512 | return r; | |
513 | } | |
514 | ||
515 | if (debug) | |
516 | pam_syslog(handle, LOG_DEBUG, "PAM environment variable %s set based on user record.", e); | |
517 | ||
518 | return PAM_SUCCESS; | |
519 | } | |
520 | ||
521 | static int apply_user_record_settings(pam_handle_t *handle, UserRecord *ur, bool debug) { | |
522 | char **i; | |
523 | int r; | |
524 | ||
525 | assert(handle); | |
526 | assert(ur); | |
527 | ||
528 | if (ur->umask != MODE_INVALID) { | |
529 | umask(ur->umask); | |
530 | ||
531 | if (debug) | |
532 | pam_syslog(handle, LOG_DEBUG, "Set user umask to %04o based on user record.", ur->umask); | |
533 | } | |
534 | ||
535 | STRV_FOREACH(i, ur->environment) { | |
536 | _cleanup_free_ char *n = NULL; | |
537 | const char *e; | |
538 | ||
539 | assert_se(e = strchr(*i, '=')); /* environment was already validated while parsing JSON record, this thus must hold */ | |
540 | ||
541 | n = strndup(*i, e - *i); | |
542 | if (!n) | |
543 | return pam_log_oom(handle); | |
544 | ||
545 | if (pam_getenv(handle, n)) { | |
546 | if (debug) | |
547 | pam_syslog(handle, LOG_DEBUG, "PAM environment variable $%s already set, not changing based on record.", *i); | |
548 | continue; | |
549 | } | |
550 | ||
551 | r = pam_putenv_and_log(handle, *i, debug); | |
552 | if (r != PAM_SUCCESS) | |
553 | return r; | |
554 | } | |
555 | ||
556 | if (ur->email_address) { | |
557 | if (pam_getenv(handle, "EMAIL")) { | |
558 | if (debug) | |
559 | pam_syslog(handle, LOG_DEBUG, "PAM environment variable $EMAIL already set, not changing based on user record."); | |
560 | } else { | |
561 | _cleanup_free_ char *joined = NULL; | |
562 | ||
563 | joined = strjoin("EMAIL=", ur->email_address); | |
564 | if (!joined) | |
565 | return pam_log_oom(handle); | |
566 | ||
567 | r = pam_putenv_and_log(handle, joined, debug); | |
568 | if (r != PAM_SUCCESS) | |
569 | return r; | |
570 | } | |
571 | } | |
572 | ||
573 | if (ur->time_zone) { | |
574 | if (pam_getenv(handle, "TZ")) { | |
575 | if (debug) | |
576 | pam_syslog(handle, LOG_DEBUG, "PAM environment variable $TZ already set, not changing based on user record."); | |
577 | } else if (!timezone_is_valid(ur->time_zone, LOG_DEBUG)) { | |
578 | if (debug) | |
579 | pam_syslog(handle, LOG_DEBUG, "Time zone specified in user record is not valid locally, not setting $TZ."); | |
580 | } else { | |
581 | _cleanup_free_ char *joined = NULL; | |
582 | ||
583 | joined = strjoin("TZ=:", ur->time_zone); | |
584 | if (!joined) | |
585 | return pam_log_oom(handle); | |
586 | ||
587 | r = pam_putenv_and_log(handle, joined, debug); | |
588 | if (r != PAM_SUCCESS) | |
589 | return r; | |
590 | } | |
591 | } | |
592 | ||
593 | if (ur->preferred_language) { | |
594 | if (pam_getenv(handle, "LANG")) { | |
595 | if (debug) | |
596 | pam_syslog(handle, LOG_DEBUG, "PAM environment variable $LANG already set, not changing based on user record."); | |
a10f5d05 | 597 | } else if (locale_is_installed(ur->preferred_language) <= 0) { |
46cdbd49 | 598 | if (debug) |
a10f5d05 | 599 | pam_syslog(handle, LOG_DEBUG, "Preferred language specified in user record is not valid or not installed, not setting $LANG."); |
46cdbd49 BR |
600 | } else { |
601 | _cleanup_free_ char *joined = NULL; | |
602 | ||
603 | joined = strjoin("LANG=", ur->preferred_language); | |
604 | if (!joined) | |
605 | return pam_log_oom(handle); | |
606 | ||
607 | r = pam_putenv_and_log(handle, joined, debug); | |
608 | if (r != PAM_SUCCESS) | |
609 | return r; | |
610 | } | |
611 | } | |
612 | ||
613 | if (nice_is_valid(ur->nice_level)) { | |
614 | if (nice(ur->nice_level) < 0) | |
615 | pam_syslog(handle, LOG_ERR, "Failed to set nice level to %i, ignoring: %s", ur->nice_level, strerror_safe(errno)); | |
616 | else if (debug) | |
617 | pam_syslog(handle, LOG_DEBUG, "Nice level set, based on user record."); | |
618 | } | |
619 | ||
620 | for (int rl = 0; rl < _RLIMIT_MAX; rl++) { | |
621 | ||
622 | if (!ur->rlimits[rl]) | |
623 | continue; | |
624 | ||
625 | r = setrlimit_closest(rl, ur->rlimits[rl]); | |
626 | if (r < 0) | |
627 | pam_syslog(handle, LOG_ERR, "Failed to set resource limit %s, ignoring: %s", rlimit_to_string(rl), strerror_safe(r)); | |
628 | else if (debug) | |
629 | pam_syslog(handle, LOG_DEBUG, "Resource limit %s set, based on user record.", rlimit_to_string(rl)); | |
630 | } | |
631 | ||
632 | return PAM_SUCCESS; | |
633 | } | |
634 | ||
a10f5d05 MB |
635 | static int configure_runtime_directory( |
636 | pam_handle_t *handle, | |
637 | UserRecord *ur, | |
638 | const char *rt) { | |
639 | ||
640 | int r; | |
641 | ||
642 | assert(handle); | |
643 | assert(ur); | |
644 | assert(rt); | |
645 | ||
646 | if (!validate_runtime_directory(handle, rt, ur->uid)) | |
647 | return PAM_SUCCESS; | |
648 | ||
649 | r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0); | |
650 | if (r != PAM_SUCCESS) { | |
651 | pam_syslog(handle, LOG_ERR, "Failed to set runtime dir: %s", pam_strerror(handle, r)); | |
652 | return r; | |
653 | } | |
654 | ||
655 | return export_legacy_dbus_address(handle, rt); | |
656 | } | |
657 | ||
663996b3 MS |
658 | _public_ PAM_EXTERN int pam_sm_open_session( |
659 | pam_handle_t *handle, | |
660 | int flags, | |
661 | int argc, const char **argv) { | |
662 | ||
4c89c718 | 663 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
b012e921 | 664 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; |
60f067b4 | 665 | const char |
46cdbd49 | 666 | *id, *object_path, *runtime_path, |
60f067b4 JS |
667 | *service = NULL, |
668 | *tty = NULL, *display = NULL, | |
669 | *remote_user = NULL, *remote_host = NULL, | |
670 | *seat = NULL, | |
671 | *type = NULL, *class = NULL, | |
6e866b33 | 672 | *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL, *desktop_pam = NULL, |
e1f67bc7 | 673 | *memory_max = NULL, *tasks_max = NULL, *cpu_weight = NULL, *io_weight = NULL, *runtime_max_sec = NULL; |
4c89c718 | 674 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; |
46cdbd49 | 675 | _cleanup_(user_record_unrefp) UserRecord *ur = NULL; |
60f067b4 JS |
676 | int session_fd = -1, existing, r; |
677 | bool debug = false, remote; | |
663996b3 | 678 | uint32_t vtnr = 0; |
60f067b4 | 679 | uid_t original_uid; |
663996b3 MS |
680 | |
681 | assert(handle); | |
682 | ||
663996b3 MS |
683 | if (parse_argv(handle, |
684 | argc, argv, | |
14228c0d | 685 | &class_pam, |
60f067b4 | 686 | &type_pam, |
6e866b33 | 687 | &desktop_pam, |
60f067b4 JS |
688 | &debug) < 0) |
689 | return PAM_SESSION_ERR; | |
690 | ||
691 | if (debug) | |
692 | pam_syslog(handle, LOG_DEBUG, "pam-systemd initializing"); | |
663996b3 | 693 | |
46cdbd49 | 694 | r = acquire_user_record(handle, &ur); |
e1f67bc7 | 695 | if (r != PAM_SUCCESS) |
60f067b4 | 696 | return r; |
663996b3 | 697 | |
a10f5d05 MB |
698 | /* Make most of this a NOP on non-logind systems */ |
699 | if (!logind_running()) | |
700 | goto success; | |
701 | ||
663996b3 MS |
702 | /* Make sure we don't enter a loop by talking to |
703 | * systemd-logind when it is actually waiting for the | |
704 | * background to finish start-up. If the service is | |
14228c0d | 705 | * "systemd-user" we simply set XDG_RUNTIME_DIR and |
663996b3 MS |
706 | * leave. */ |
707 | ||
e1f67bc7 | 708 | (void) pam_get_item(handle, PAM_SERVICE, (const void**) &service); |
14228c0d | 709 | if (streq_ptr(service, "systemd-user")) { |
7c20daf6 | 710 | char rt[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)]; |
663996b3 | 711 | |
46cdbd49 | 712 | xsprintf(rt, "/run/user/"UID_FMT, ur->uid); |
a10f5d05 | 713 | r = configure_runtime_directory(handle, ur, rt); |
7c20daf6 FS |
714 | if (r != PAM_SUCCESS) |
715 | return r; | |
716 | ||
a10f5d05 | 717 | goto success; |
663996b3 MS |
718 | } |
719 | ||
60f067b4 | 720 | /* Otherwise, we ask logind to create a session for us */ |
663996b3 | 721 | |
e1f67bc7 MB |
722 | (void) pam_get_item(handle, PAM_XDISPLAY, (const void**) &display); |
723 | (void) pam_get_item(handle, PAM_TTY, (const void**) &tty); | |
724 | (void) pam_get_item(handle, PAM_RUSER, (const void**) &remote_user); | |
725 | (void) pam_get_item(handle, PAM_RHOST, (const void**) &remote_host); | |
663996b3 | 726 | |
6e866b33 MB |
727 | seat = getenv_harder(handle, "XDG_SEAT", NULL); |
728 | cvtnr = getenv_harder(handle, "XDG_VTNR", NULL); | |
729 | type = getenv_harder(handle, "XDG_SESSION_TYPE", type_pam); | |
730 | class = getenv_harder(handle, "XDG_SESSION_CLASS", class_pam); | |
731 | desktop = getenv_harder(handle, "XDG_SESSION_DESKTOP", desktop_pam); | |
60f067b4 | 732 | |
663996b3 | 733 | tty = strempty(tty); |
663996b3 MS |
734 | |
735 | if (strchr(tty, ':')) { | |
6e866b33 MB |
736 | /* A tty with a colon is usually an X11 display, placed there to show up in utmp. We rearrange things |
737 | * and don't pretend that an X display was a tty. */ | |
663996b3 MS |
738 | if (isempty(display)) |
739 | display = tty; | |
60f067b4 | 740 | tty = NULL; |
6e866b33 | 741 | |
663996b3 | 742 | } else if (streq(tty, "cron")) { |
6e866b33 MB |
743 | /* cron is setting PAM_TTY to "cron" for some reason (the commit carries no information why, but |
744 | * probably because it wants to set it to something as pam_time/pam_access/… require PAM_TTY to be set | |
745 | * (as they otherwise even try to update it!) — but cron doesn't actually allocate a TTY for its forked | |
746 | * off processes.) */ | |
663996b3 | 747 | type = "unspecified"; |
60f067b4 JS |
748 | class = "background"; |
749 | tty = NULL; | |
6e866b33 | 750 | |
663996b3 | 751 | } else if (streq(tty, "ssh")) { |
6e866b33 MB |
752 | /* ssh has been setting PAM_TTY to "ssh" (for the same reason as cron does this, see above. For further |
753 | * details look for "PAM_TTY_KLUDGE" in the openssh sources). */ | |
663996b3 | 754 | type ="tty"; |
60f067b4 | 755 | class = "user"; |
6e866b33 MB |
756 | tty = NULL; /* This one is particularly sad, as this means that ssh sessions — even though usually |
757 | * associated with a pty — won't be tracked by their tty in logind. This is because ssh | |
758 | * does the PAM session registration early for new connections, and registers a pty only | |
759 | * much later (this is because it doesn't know yet if it needs one at all, as whether to | |
760 | * register a pty or not is negotiated much later in the protocol). */ | |
761 | ||
f5e65279 MB |
762 | } else |
763 | /* Chop off leading /dev prefix that some clients specify, but others do not. */ | |
764 | tty = skip_dev_prefix(tty); | |
663996b3 MS |
765 | |
766 | /* If this fails vtnr will be 0, that's intended */ | |
767 | if (!isempty(cvtnr)) | |
e3bff60a | 768 | (void) safe_atou32(cvtnr, &vtnr); |
663996b3 | 769 | |
60f067b4 | 770 | if (!isempty(display) && !vtnr) { |
663996b3 | 771 | if (isempty(seat)) |
6e866b33 | 772 | (void) get_seat_from_display(display, &seat, &vtnr); |
663996b3 | 773 | else if (streq(seat, "seat0")) |
6e866b33 | 774 | (void) get_seat_from_display(display, NULL, &vtnr); |
663996b3 MS |
775 | } |
776 | ||
60f067b4 | 777 | if (seat && !streq(seat, "seat0") && vtnr != 0) { |
6e866b33 MB |
778 | if (debug) |
779 | pam_syslog(handle, LOG_DEBUG, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat); | |
60f067b4 JS |
780 | vtnr = 0; |
781 | } | |
782 | ||
783 | if (isempty(type)) | |
663996b3 | 784 | type = !isempty(display) ? "x11" : |
60f067b4 | 785 | !isempty(tty) ? "tty" : "unspecified"; |
663996b3 | 786 | |
663996b3 MS |
787 | if (isempty(class)) |
788 | class = streq(type, "unspecified") ? "background" : "user"; | |
789 | ||
e842803a | 790 | remote = !isempty(remote_host) && !is_localhost(remote_host); |
60f067b4 | 791 | |
b012e921 MB |
792 | (void) pam_get_data(handle, "systemd.memory_max", (const void **)&memory_max); |
793 | (void) pam_get_data(handle, "systemd.tasks_max", (const void **)&tasks_max); | |
794 | (void) pam_get_data(handle, "systemd.cpu_weight", (const void **)&cpu_weight); | |
795 | (void) pam_get_data(handle, "systemd.io_weight", (const void **)&io_weight); | |
e1f67bc7 | 796 | (void) pam_get_data(handle, "systemd.runtime_max_sec", (const void **)&runtime_max_sec); |
b012e921 | 797 | |
60f067b4 | 798 | /* Talk to logind over the message bus */ |
663996b3 | 799 | |
46cdbd49 BR |
800 | r = pam_acquire_bus_connection(handle, &bus); |
801 | if (r != PAM_SUCCESS) | |
802 | return r; | |
663996b3 | 803 | |
b012e921 | 804 | if (debug) { |
663996b3 | 805 | pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: " |
e735f4d4 | 806 | "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", |
46cdbd49 | 807 | ur->uid, getpid_cached(), |
60f067b4 JS |
808 | strempty(service), |
809 | type, class, strempty(desktop), | |
810 | strempty(seat), vtnr, strempty(tty), strempty(display), | |
811 | yes_no(remote), strempty(remote_user), strempty(remote_host)); | |
b012e921 | 812 | pam_syslog(handle, LOG_DEBUG, "Session limits: " |
e1f67bc7 MB |
813 | "memory_max=%s tasks_max=%s cpu_weight=%s io_weight=%s runtime_max_sec=%s", |
814 | strna(memory_max), strna(tasks_max), strna(cpu_weight), strna(io_weight), strna(runtime_max_sec)); | |
b012e921 MB |
815 | } |
816 | ||
a10f5d05 | 817 | r = bus_message_new_method_call(bus, &m, bus_login_mgr, "CreateSession"); |
46cdbd49 BR |
818 | if (r < 0) |
819 | return pam_bus_log_create_error(handle, r); | |
b012e921 MB |
820 | |
821 | r = sd_bus_message_append(m, "uusssssussbss", | |
46cdbd49 | 822 | (uint32_t) ur->uid, |
6e866b33 | 823 | 0, |
b012e921 MB |
824 | service, |
825 | type, | |
826 | class, | |
827 | desktop, | |
828 | seat, | |
829 | vtnr, | |
830 | tty, | |
831 | display, | |
832 | remote, | |
833 | remote_user, | |
834 | remote_host); | |
46cdbd49 BR |
835 | if (r < 0) |
836 | return pam_bus_log_create_error(handle, r); | |
b012e921 MB |
837 | |
838 | r = sd_bus_message_open_container(m, 'a', "(sv)"); | |
46cdbd49 BR |
839 | if (r < 0) |
840 | return pam_bus_log_create_error(handle, r); | |
b012e921 MB |
841 | |
842 | r = append_session_memory_max(handle, m, memory_max); | |
46cdbd49 BR |
843 | if (r != PAM_SUCCESS) |
844 | return r; | |
b012e921 | 845 | |
e1f67bc7 | 846 | r = append_session_runtime_max_sec(handle, m, runtime_max_sec); |
46cdbd49 BR |
847 | if (r != PAM_SUCCESS) |
848 | return r; | |
e1f67bc7 | 849 | |
b012e921 | 850 | r = append_session_tasks_max(handle, m, tasks_max); |
46cdbd49 BR |
851 | if (r != PAM_SUCCESS) |
852 | return r; | |
b012e921 MB |
853 | |
854 | r = append_session_cg_weight(handle, m, cpu_weight, "CPUWeight"); | |
46cdbd49 BR |
855 | if (r != PAM_SUCCESS) |
856 | return r; | |
b012e921 MB |
857 | |
858 | r = append_session_cg_weight(handle, m, io_weight, "IOWeight"); | |
46cdbd49 BR |
859 | if (r != PAM_SUCCESS) |
860 | return r; | |
b012e921 MB |
861 | |
862 | r = sd_bus_message_close_container(m); | |
46cdbd49 BR |
863 | if (r < 0) |
864 | return pam_bus_log_create_error(handle, r); | |
60f067b4 | 865 | |
e1f67bc7 | 866 | r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply); |
60f067b4 | 867 | if (r < 0) { |
fb183854 | 868 | if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) { |
6e866b33 | 869 | if (debug) |
7c20daf6 | 870 | pam_syslog(handle, LOG_DEBUG, "Not creating session: %s", bus_error_message(&error, r)); |
a10f5d05 MB |
871 | |
872 | /* We are already in a session, don't do anything */ | |
873 | goto success; | |
fb183854 MP |
874 | } else { |
875 | pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r)); | |
e1f67bc7 | 876 | return PAM_SESSION_ERR; |
fb183854 | 877 | } |
663996b3 MS |
878 | } |
879 | ||
60f067b4 JS |
880 | r = sd_bus_message_read(reply, |
881 | "soshusub", | |
882 | &id, | |
883 | &object_path, | |
884 | &runtime_path, | |
885 | &session_fd, | |
886 | &original_uid, | |
887 | &seat, | |
888 | &vtnr, | |
889 | &existing); | |
46cdbd49 BR |
890 | if (r < 0) |
891 | return pam_bus_log_parse_error(handle, r); | |
663996b3 MS |
892 | |
893 | if (debug) | |
894 | pam_syslog(handle, LOG_DEBUG, "Reply from logind: " | |
60f067b4 JS |
895 | "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u", |
896 | id, object_path, runtime_path, session_fd, seat, vtnr, original_uid); | |
663996b3 | 897 | |
6e866b33 MB |
898 | r = update_environment(handle, "XDG_SESSION_ID", id); |
899 | if (r != PAM_SUCCESS) | |
60f067b4 | 900 | return r; |
663996b3 | 901 | |
46cdbd49 | 902 | if (original_uid == ur->uid) { |
a10f5d05 MB |
903 | /* Don't set $XDG_RUNTIME_DIR if the user we now authenticated for does not match the |
904 | * original user of the session. We do this in order not to result in privileged apps | |
905 | * clobbering the runtime directory unnecessarily. */ | |
7c20daf6 | 906 | |
a10f5d05 | 907 | r = configure_runtime_directory(handle, ur, runtime_path); |
7c20daf6 FS |
908 | if (r != PAM_SUCCESS) |
909 | return r; | |
663996b3 MS |
910 | } |
911 | ||
6e866b33 MB |
912 | /* Most likely we got the session/type/class from environment variables, but might have gotten the data |
913 | * somewhere else (for example PAM module parameters). Let's now update the environment variables, so that this | |
914 | * data is inherited into the session processes, and programs can rely on them to be initialized. */ | |
915 | ||
916 | r = update_environment(handle, "XDG_SESSION_TYPE", type); | |
917 | if (r != PAM_SUCCESS) | |
918 | return r; | |
919 | ||
920 | r = update_environment(handle, "XDG_SESSION_CLASS", class); | |
921 | if (r != PAM_SUCCESS) | |
922 | return r; | |
923 | ||
924 | r = update_environment(handle, "XDG_SESSION_DESKTOP", desktop); | |
925 | if (r != PAM_SUCCESS) | |
926 | return r; | |
927 | ||
928 | r = update_environment(handle, "XDG_SEAT", seat); | |
929 | if (r != PAM_SUCCESS) | |
930 | return r; | |
663996b3 MS |
931 | |
932 | if (vtnr > 0) { | |
60f067b4 JS |
933 | char buf[DECIMAL_STR_MAX(vtnr)]; |
934 | sprintf(buf, "%u", vtnr); | |
663996b3 | 935 | |
6e866b33 MB |
936 | r = update_environment(handle, "XDG_VTNR", buf); |
937 | if (r != PAM_SUCCESS) | |
60f067b4 | 938 | return r; |
663996b3 MS |
939 | } |
940 | ||
941 | r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL); | |
942 | if (r != PAM_SUCCESS) { | |
e1f67bc7 | 943 | pam_syslog(handle, LOG_ERR, "Failed to install existing flag: %s", pam_strerror(handle, r)); |
663996b3 MS |
944 | return r; |
945 | } | |
946 | ||
947 | if (session_fd >= 0) { | |
60f067b4 JS |
948 | session_fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3); |
949 | if (session_fd < 0) { | |
950 | pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m"); | |
951 | return PAM_SESSION_ERR; | |
952 | } | |
953 | ||
db2df898 | 954 | r = pam_set_data(handle, "systemd.session-fd", FD_TO_PTR(session_fd), NULL); |
663996b3 | 955 | if (r != PAM_SUCCESS) { |
e1f67bc7 | 956 | pam_syslog(handle, LOG_ERR, "Failed to install session fd: %s", pam_strerror(handle, r)); |
60f067b4 | 957 | safe_close(session_fd); |
663996b3 MS |
958 | return r; |
959 | } | |
960 | } | |
961 | ||
a10f5d05 | 962 | success: |
46cdbd49 BR |
963 | r = apply_user_record_settings(handle, ur, debug); |
964 | if (r != PAM_SUCCESS) | |
965 | return r; | |
966 | ||
967 | /* Let's release the D-Bus connection, after all the session might live quite a long time, and we are | |
968 | * not going to use the bus connection in that time, so let's better close before the daemon kicks us | |
969 | * off because we are not processing anything. */ | |
970 | (void) pam_release_bus_connection(handle); | |
60f067b4 | 971 | return PAM_SUCCESS; |
663996b3 MS |
972 | } |
973 | ||
974 | _public_ PAM_EXTERN int pam_sm_close_session( | |
975 | pam_handle_t *handle, | |
976 | int flags, | |
977 | int argc, const char **argv) { | |
978 | ||
60f067b4 | 979 | const void *existing = NULL; |
20a6e51f | 980 | bool debug = false; |
663996b3 | 981 | const char *id; |
663996b3 MS |
982 | int r; |
983 | ||
984 | assert(handle); | |
985 | ||
20a6e51f MB |
986 | if (parse_argv(handle, |
987 | argc, argv, | |
988 | NULL, | |
989 | NULL, | |
990 | NULL, | |
991 | &debug) < 0) | |
992 | return PAM_SESSION_ERR; | |
993 | ||
994 | if (debug) | |
995 | pam_syslog(handle, LOG_DEBUG, "pam-systemd shutting down"); | |
996 | ||
663996b3 MS |
997 | /* Only release session if it wasn't pre-existing when we |
998 | * tried to create it */ | |
6e866b33 | 999 | (void) pam_get_data(handle, "systemd.existing", &existing); |
663996b3 MS |
1000 | |
1001 | id = pam_getenv(handle, "XDG_SESSION_ID"); | |
1002 | if (id && !existing) { | |
46cdbd49 BR |
1003 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
1004 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; | |
663996b3 | 1005 | |
46cdbd49 BR |
1006 | /* Before we go and close the FIFO we need to tell logind that this is a clean session |
1007 | * shutdown, so that it doesn't just go and slaughter us immediately after closing the fd */ | |
663996b3 | 1008 | |
46cdbd49 BR |
1009 | r = pam_acquire_bus_connection(handle, &bus); |
1010 | if (r != PAM_SUCCESS) | |
1011 | return r; | |
663996b3 | 1012 | |
a10f5d05 | 1013 | r = bus_call_method(bus, bus_login_mgr, "ReleaseSession", &error, NULL, "s", id); |
60f067b4 JS |
1014 | if (r < 0) { |
1015 | pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error, r)); | |
1016 | return PAM_SESSION_ERR; | |
663996b3 MS |
1017 | } |
1018 | } | |
1019 | ||
46cdbd49 BR |
1020 | /* Note that we are knowingly leaking the FIFO fd here. This way, logind can watch us die. If we |
1021 | * closed it here it would not have any clue when that is completed. Given that one cannot really | |
1022 | * have multiple PAM sessions open from the same process this means we will leak one FD at max. */ | |
663996b3 | 1023 | |
60f067b4 | 1024 | return PAM_SUCCESS; |
663996b3 | 1025 | } |