()
| 41 | }; |
| 42 | |
| 43 | function AccountsPage() { |
| 44 | const [isGroupsExpanded, setIsGroupsExpanded] = useState(false); |
| 45 | const { path } = usePageLocation(); |
| 46 | const accounts = useFile("/etc/passwd", { syntax: etc_passwd_syntax }); |
| 47 | const groups = useFile("/etc/group", { syntax: etc_group_syntax }); |
| 48 | const shells = useFile("/etc/shells", { syntax: etc_shells_syntax }); |
| 49 | const current_user_info = useLoggedInUser(); |
| 50 | |
| 51 | // Handle the case where logindef == null, i.e. the file does not exist. |
| 52 | // While that's unusual, "empty /etc" is a goal, and it shouldn't crash the page. |
| 53 | const [min_gid, setMinGid] = useState(500); |
| 54 | const [max_gid, setMaxGid] = useState(60000); |
| 55 | const [min_uid, setMinUid] = useState(500); |
| 56 | const [max_uid, setMaxUid] = useState(60000); |
| 57 | const [details, setDetails] = useState(null); |
| 58 | |
| 59 | useInit(async () => { |
| 60 | const logind_client = cockpit.dbus("org.freedesktop.login1"); |
| 61 | |
| 62 | const debouncedGetLoginDetails = debounce(100, () => { |
| 63 | getLoginDetails(logind_client).then(setDetails); |
| 64 | }); |
| 65 | |
| 66 | /* We are mostly interested in UserNew/UserRemoved. But SessionRemoved happens immediately after logout, |
| 67 | * while UserRemoved lags behind due to the "State: closing" period when the user's systemd instance |
| 68 | * etc. are being cleaned up. Also, there's not that many signals and this is debounced, so just react to all |
| 69 | * of them. See https://www.freedesktop.org/wiki/Software/systemd/logind/ */ |
| 70 | logind_client.subscribe({ |
| 71 | interface: "org.freedesktop.login1.Manager", |
| 72 | path: "/org/freedesktop/login1", |
| 73 | }, debouncedGetLoginDetails); |
| 74 | |
| 75 | let handleUtmp; |
| 76 | |
| 77 | // Watch /etc/shadow to register lock/unlock/expire changes; but avoid reading it, it's sensitive data |
| 78 | const handleShadow = cockpit.file("/etc/shadow", { superuser: "try" }); |
| 79 | handleShadow.watch(() => debouncedGetLoginDetails(), { read: false }); |
| 80 | |
| 81 | let handleLogindef; |
| 82 | try { |
| 83 | await fsinfo("/etc/login.defs", []); |
| 84 | handleLogindef = cockpit.file("/etc/login.defs"); |
| 85 | } catch (ex) { |
| 86 | handleLogindef = cockpit.file("/usr/etc/login.defs"); |
| 87 | } |
| 88 | |
| 89 | handleLogindef.watch((logindef) => { |
| 90 | if (logindef === null) |
| 91 | return; |
| 92 | |
| 93 | const minGid = parseInt(logindef.match(/^GID_MIN\s+(\d+)/m)[1]); |
| 94 | const maxGid = parseInt(logindef.match(/^GID_MAX\s+(\d+)/m)[1]); |
| 95 | const minUid = parseInt(logindef.match(/^UID_MIN\s+(\d+)/m)[1]); |
| 96 | const maxUid = parseInt(logindef.match(/^UID_MAX\s+(\d+)/m)[1]); |
| 97 | |
| 98 | if (minGid) |
| 99 | setMinGid(minGid); |
| 100 | if (maxGid) |
nothing calls this directly
no test coverage detected