diff options
| author | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
|---|---|---|
| committer | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
| commit | 76424950e373d3b04ac3dd13019151bfba3e8423 (patch) | |
| tree | 4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/detection/users | |
Add the files
Diffstat (limited to 'src/detection/users')
| -rw-r--r-- | src/detection/users/users.h | 14 | ||||
| -rw-r--r-- | src/detection/users/users_linux.c | 204 | ||||
| -rw-r--r-- | src/detection/users/users_nosupport.c | 6 | ||||
| -rw-r--r-- | src/detection/users/users_obsd.c | 39 | ||||
| -rw-r--r-- | src/detection/users/users_windows.c | 61 |
5 files changed, 324 insertions, 0 deletions
diff --git a/src/detection/users/users.h b/src/detection/users/users.h new file mode 100644 index 0000000..8f24cc0 --- /dev/null +++ b/src/detection/users/users.h @@ -0,0 +1,14 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/users/option.h" + +typedef struct FFUserResult { + FFstrbuf name; + FFstrbuf hostName; + FFstrbuf clientIp; + FFstrbuf sessionName; + uint64_t loginTime; // ms +} FFUserResult; + +const char* ffDetectUsers(FFUsersOptions* options, FFlist* users); diff --git a/src/detection/users/users_linux.c b/src/detection/users/users_linux.c new file mode 100644 index 0000000..bc84eed --- /dev/null +++ b/src/detection/users/users_linux.c @@ -0,0 +1,204 @@ +#include "common/io.h" +#include "common/properties.h" +#include "fastfetch.h" +#include "users.h" + +#include <unistd.h> + +#if FF_HAVE_UTMPX + #include <utmpx.h> +#else + // for Android compatibility + #include <utmp.h> + #define utmpx utmp + #define setutxent setutent + #define getutxent getutent +#endif +#if __linux__ || __GNU__ + #include <netinet/in.h> + #include <arpa/inet.h> +#endif + +#if __linux__ +bool detectUserBySystemd(const FFstrbuf* pathUsers, FFlist* users) { + FF_STRBUF_AUTO_DESTROY state = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY userName = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY loginTime = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY sessions = ffStrbufCreate(); + + // WARNING: This is private data. Do not parse + if (!ffParsePropFileValues(pathUsers->chars, 4, (FFpropquery[]) { + { "NAME=", &userName }, + { "STATE=", &state }, + { "REALTIME=", &loginTime }, + { "ONLINE_SESSIONS=", &sessions }, + }) || + !ffStrbufEqualS(&state, "active")) { + return false; + } + + FFUserResult* user = FF_LIST_ADD(FFUserResult, *users); + ffStrbufInitMove(&user->name, &userName); + ffStrbufInit(&user->hostName); + ffStrbufInit(&user->sessionName); + ffStrbufInit(&user->clientIp); + ffStrbufSubstrBefore(&loginTime, loginTime.length - 3); // converts us to ms + user->loginTime = ffStrbufToUInt(&loginTime, 0); + + FF_STRBUF_AUTO_DESTROY pathSessions = ffStrbufCreateS("/run/systemd/sessions/"); + const uint32_t pathSessionsBaseLen = pathSessions.length; + + FF_STRBUF_AUTO_DESTROY tty = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY remoteHost = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY service = ffStrbufCreate(); + + char* token = NULL; + size_t n = 0; + while (ffStrbufGetdelim(&token, &n, ' ', &sessions)) { + ffStrbufSubstrBefore(&pathSessions, pathSessionsBaseLen); + ffStrbufAppendS(&pathSessions, token); + + ffStrbufClear(&remoteHost); + ffStrbufClear(&service); + ffStrbufClear(&tty); + ffStrbufClear(&loginTime); + + // WARNING: This is private data. Do not parse + if (ffParsePropFileValues(pathSessions.chars, 4, (FFpropquery[]) { + { "REMOTE_HOST=", &remoteHost }, + { "TTY=", &tty }, + { "SERVICE=", &service }, + { "REALTIME=", &loginTime }, + }) && + !ffStrbufEqualS(&service, "systemd-user")) { + if (remoteHost.length) { + ffStrbufTrimRight(&remoteHost, ']'); + ffStrbufTrimLeft(&remoteHost, '['); + ffStrbufInitMove(&user->hostName, &remoteHost); + } else { + ffStrbufSetStatic(&user->hostName, "localhost"); + } + ffStrbufInitMove(&user->sessionName, tty.length ? &tty : &service); + if (loginTime.length) { + ffStrbufSubstrBefore(&loginTime, loginTime.length - 3); // converts us to ms + user->loginTime = ffStrbufToUInt(&loginTime, 0); + } + break; + } + } + + return true; +} + +const char* detectBySystemd(FFUsersOptions* options, FFlist* users) { + // For some reason, debian/ubuntu no longer updates `/var/run/utmp` (#2064) + // Query systemd instead + FF_STRBUF_AUTO_DESTROY pathUsers = ffStrbufCreateS("/run/systemd/users/"); + + if (options->myselfOnly) { + ffStrbufAppendUInt(&pathUsers, instance.state.platform.uid); + detectUserBySystemd(&pathUsers, users); + } else { + const uint32_t pathUsersBaseLen = pathUsers.length; + FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pathUsers.chars); + if (!dirp) { + return "opendir(\"/run/systemd/users/\") failed"; + } + + struct dirent* entry; + while ((entry = readdir(dirp))) { + if (entry->d_type != DT_REG) { + continue; + } + + ffStrbufAppendS(&pathUsers, entry->d_name); + detectUserBySystemd(&pathUsers, users); + ffStrbufSubstrBefore(&pathUsers, pathUsersBaseLen); + } + } + return NULL; +} +#endif + +#if __linux__ || __GNU__ +static void fillUtmpIpAddr(FFUserResult* user, struct utmpx* n) { + bool isIpv6 = false; + for (int i = 1; i < 4; ++i) { + if (n->ut_addr_v6[i] != 0) { + isIpv6 = true; + break; + } + } + + if (isIpv6) { + char ipv6_str[INET6_ADDRSTRLEN]; + if (inet_ntop(AF_INET6, n->ut_addr_v6, ipv6_str, INET6_ADDRSTRLEN) != NULL) { + ffStrbufSetS(&user->clientIp, ipv6_str); + } + } else if (n->ut_addr_v6[0] != 0) { + char ipv4_str[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, n->ut_addr_v6, ipv4_str, INET_ADDRSTRLEN) != NULL) { + ffStrbufSetS(&user->clientIp, ipv4_str); + } + } +} +#else +static void fillUtmpIpAddr(FF_A_UNUSED FFUserResult* user, FF_A_UNUSED struct utmpx* n) { +} +#endif + +const char* detectByUtmp(FFUsersOptions* options, FFlist* users) { + struct utmpx* n = NULL; + setutxent(); + +next: + while ((n = getutxent())) { + if (n->ut_type != USER_PROCESS) { + continue; + } + + if (options->myselfOnly && !ffStrbufEqualS(&instance.state.platform.userName, n->ut_user)) { + continue; + } + + FF_LIST_FOR_EACH (FFUserResult, user, *users) { + if (ffStrbufEqualS(&user->name, n->ut_user)) { + uint64_t newLoginTime = (uint64_t) n->ut_tv.tv_sec * 1000 + (uint64_t) n->ut_tv.tv_usec / 1000; + if (newLoginTime > user->loginTime) { + ffStrbufSetS(&user->hostName, n->ut_host); + ffStrbufSetS(&user->sessionName, n->ut_line); + fillUtmpIpAddr(user, n); + user->loginTime = newLoginTime; + } + goto next; + } + } + + FFUserResult* user = FF_LIST_ADD(FFUserResult, *users); + ffStrbufInitS(&user->name, n->ut_user); + ffStrbufInitS(&user->hostName, n->ut_host); + ffStrbufInitS(&user->sessionName, n->ut_line); + ffStrbufInit(&user->clientIp); + fillUtmpIpAddr(user, n); + user->loginTime = (uint64_t) n->ut_tv.tv_sec * 1000 + (uint64_t) n->ut_tv.tv_usec / 1000; + } + + endutxent(); + + return NULL; +} + +const char* ffDetectUsers(FFUsersOptions* options, FFlist* users) { + const char* err = detectByUtmp(options, users); + if (err) { + return err; + } + +#if __linux__ + if (users->length == 0) { + detectBySystemd(options, users); + } +#endif + + return NULL; +} diff --git a/src/detection/users/users_nosupport.c b/src/detection/users/users_nosupport.c new file mode 100644 index 0000000..0a7e30a --- /dev/null +++ b/src/detection/users/users_nosupport.c @@ -0,0 +1,6 @@ +#include "fastfetch.h" +#include "users.h" + +const char* ffDetectUsers(FFUsersOptions* options, FFlist* users) { + return "Not supported on this platform"; +} diff --git a/src/detection/users/users_obsd.c b/src/detection/users/users_obsd.c new file mode 100644 index 0000000..97a22f0 --- /dev/null +++ b/src/detection/users/users_obsd.c @@ -0,0 +1,39 @@ +#include "fastfetch.h" +#include "users.h" +#include "common/io.h" + +#include <utmp.h> + +const char* ffDetectUsers(FF_A_UNUSED FFUsersOptions* options, FFlist* users) { + FF_AUTO_CLOSE_FILE FILE* fp = fopen(_PATH_UTMP, "r"); + if (!fp) { + return "fopen(_PATH_UTMP, r) failed"; + } + + struct utmp n; +next: + while (fread(&n, sizeof(n), 1, fp) == 1) { + if (!n.ut_name[0]) { + continue; + } + + if (options->myselfOnly && !ffStrbufEqualS(&instance.state.platform.userName, n.ut_name)) { + continue; + } + + FF_LIST_FOR_EACH (FFUserResult, user, *users) { + if (ffStrbufEqualS(&user->name, n.ut_name)) { + goto next; + } + } + + FFUserResult* user = FF_LIST_ADD(FFUserResult, *users); + ffStrbufInitS(&user->name, n.ut_name); + ffStrbufInitS(&user->hostName, n.ut_host); + ffStrbufInitS(&user->sessionName, n.ut_line); + ffStrbufInit(&user->clientIp); + user->loginTime = (uint64_t) n.ut_time * 1000; + } + + return NULL; +} diff --git a/src/detection/users/users_windows.c b/src/detection/users/users_windows.c new file mode 100644 index 0000000..4cde44e --- /dev/null +++ b/src/detection/users/users_windows.c @@ -0,0 +1,61 @@ +#include "users.h" +#include "common/windows/unicode.h" +#include "common/time.h" + +#include <windows.h> +#include <wtsapi32.h> +#include <ws2tcpip.h> + +const char* ffDetectUsers(FFUsersOptions* options, FFlist* users) { + WTS_SESSION_INFO_1W* sessionInfo; + DWORD sessionCount; + DWORD level = 1; + + if (!WTSEnumerateSessionsExW(WTS_CURRENT_SERVER_HANDLE, &level, 0, &sessionInfo, &sessionCount)) { + return "WTSEnumerateSessionsW(WTS_CURRENT_SERVER_HANDLE) failed"; + } + + for (DWORD i = 0; i < sessionCount; i++) { + WTS_SESSION_INFO_1W* session = &sessionInfo[i]; + if (session->State != WTSActive) { + continue; + } + + FF_STRBUF_AUTO_DESTROY userName = ffStrbufCreateWS(session->pUserName); + + if (options->myselfOnly && !ffStrbufEqual(&instance.state.platform.userName, &userName)) { + continue; + } + + FFUserResult* user = FF_LIST_ADD(FFUserResult, *users); + ffStrbufInitMove(&user->name, &userName); + ffStrbufInitWS(&user->hostName, session->pHostName); + ffStrbufInitWS(&user->sessionName, session->pSessionName); + ffStrbufInit(&user->clientIp); + user->loginTime = 0; + + DWORD bytes = 0; + PWTS_CLIENT_ADDRESS address = NULL; + if (WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session->SessionId, WTSClientAddress, (LPWSTR*) &address, &bytes)) { + if (address->AddressFamily == AF_INET) { + ffStrbufSetF(&user->clientIp, "%u.%u.%u.%u", address->Address[2], address->Address[3], address->Address[4], address->Address[5]); + } else if (address->AddressFamily == AF_INET6) { + char ipStr[INET6_ADDRSTRLEN]; + const char* end = RtlIpv6AddressToStringA((const IN6_ADDR*) address->Address, ipStr); + ffStrbufSetNS(&user->clientIp, (uint32_t) (end - ipStr), ipStr); + } + WTSFreeMemory(address); + } + + bytes = 0; + PWTSINFOW wtsInfo = NULL; + if (WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session->SessionId, WTSSessionInfo, (LPWSTR*) &wtsInfo, &bytes)) { + user->loginTime = ffFileTimeToUnixMs((uint64_t) wtsInfo->LogonTime.QuadPart); + WTSFreeMemory(wtsInfo); + } + } + + WTSFreeMemoryExW(WTSTypeSessionInfoLevel1, sessionInfo, 1); + + return NULL; +} |