summaryrefslogtreecommitdiffstats
path: root/src/detection/displayserver/linux/wmde.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/detection/displayserver/linux/wmde.c')
-rw-r--r--src/detection/displayserver/linux/wmde.c523
1 files changed, 523 insertions, 0 deletions
diff --git a/src/detection/displayserver/linux/wmde.c b/src/detection/displayserver/linux/wmde.c
new file mode 100644
index 0000000..f0321c8
--- /dev/null
+++ b/src/detection/displayserver/linux/wmde.c
@@ -0,0 +1,523 @@
+#include "displayserver_linux.h"
+#include "common/io.h"
+#include "common/properties.h"
+#include "common/strutil.h"
+#include "common/mallocHelper.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#if __FreeBSD__
+ #include <sys/sysctl.h>
+ #include <sys/types.h>
+ #include <sys/user.h>
+#elif __OpenBSD__
+ #include <sys/param.h>
+ #include <sys/sysctl.h>
+ #include <kvm.h>
+#elif __sun
+ #include <procfs.h>
+#elif __NetBSD__
+ #include <sys/types.h>
+ #include <sys/sysctl.h>
+#endif
+
+static const char* parseEnv(void) {
+ const char* env;
+
+ env = getenv("XDG_CURRENT_DESKTOP");
+ if (ffStrSet(env)) {
+ return env;
+ }
+
+ env = getenv("XDG_SESSION_DESKTOP");
+ if (ffStrSet(env)) {
+ return env;
+ }
+
+ env = getenv("CURRENT_DESKTOP");
+ if (ffStrSet(env)) {
+ return env;
+ }
+
+ env = getenv("SESSION_DESKTOP");
+ if (ffStrSet(env)) {
+ return env;
+ }
+
+ env = getenv("DESKTOP_SESSION");
+ if (ffStrSet(env)) {
+ return env;
+ }
+
+ if (getenv("KDE_FULL_SESSION") != NULL || getenv("KDE_SESSION_UID") != NULL || getenv("KDE_SESSION_VERSION") != NULL) {
+ return "KDE";
+ }
+
+ if (getenv("GNOME_DESKTOP_SESSION_ID") != NULL) {
+ return "GNOME";
+ }
+
+ if (getenv("MATE_DESKTOP_SESSION_ID") != NULL) {
+ return "Mate";
+ }
+
+ if (getenv("TDE_FULL_SESSION") != NULL) {
+ return "Trinity";
+ }
+
+ if (getenv("HYPRLAND_CMD") != NULL) {
+ return "Hyprland";
+ }
+
+ if (getenv("SWAYSOCK") != NULL) {
+ return "Sway";
+ }
+
+#if __linux__ && !__ANDROID__
+ if (
+ getenv("WAYLAND_DISPLAY") != NULL &&
+ ffPathExists("/mnt/wslg/", FF_PATHTYPE_DIRECTORY)) {
+ return "WSLg";
+ }
+#endif
+
+ return NULL;
+}
+
+static void applyPrettyNameIfWM(FFDisplayServerResult* result, const char* name) {
+ if (!ffStrSet(name)) {
+ return;
+ }
+
+ if (
+ ffStrEqualsIgnCase(name, "kwin") ||
+ ffStrStartsWithIgnCase(name, "kwin_") ||
+ ffStrEndsWithIgnCase(name, "-kwin_wayland") ||
+ ffStrEndsWithIgnCase(name, "-kwin_x11")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_KWIN);
+ } else if (
+ ffStrEqualsIgnCase(name, "gnome-shell") ||
+ ffStrEqualsIgnCase(name, "gnome shell") ||
+ ffStrEqualsIgnCase(name, "gnome-session-binary") ||
+ ffStrEqualsIgnCase(name, "Mutter")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_MUTTER);
+ } else if (
+ ffStrEqualsIgnCase(name, "cinnamon") ||
+ ffStrStartsWithIgnCase(name, "cinnamon-") ||
+ ffStrEqualsIgnCase(name, "Muffin") ||
+ ffStrEqualsIgnCase(name, "Mutter (Muffin)")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_MUFFIN);
+ } else if (ffStrEqualsIgnCase(name, "sway")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_SWAY);
+ } else if (ffStrEqualsIgnCase(name, "weston")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_WESTON);
+ } else if (ffStrEqualsIgnCase(name, "wayfire")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_WAYFIRE);
+ } else if (ffStrEqualsIgnCase(name, "openbox")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_OPENBOX);
+ } else if (ffStrEqualsIgnCase(name, "xfwm4")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_XFWM4);
+ } else if (ffStrEqualsIgnCase(name, "Marco") ||
+ ffStrEqualsIgnCase(name, "Metacity (Marco)")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_MARCO);
+ } else if (ffStrEqualsIgnCase(name, "xmonad")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_XMONAD);
+ } else if (ffStrEqualsIgnCase(name, "WSLg")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_WSLG);
+ } else if (ffStrEqualsIgnCase(name, "dwm")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_DWM);
+ } else if (ffStrEqualsIgnCase(name, "bspwm")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_BSPWM);
+ } else if (ffStrEqualsIgnCase(name, "tinywm")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_TINYWM);
+ } else if (ffStrEqualsIgnCase(name, "qtile")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_QTILE);
+ } else if (ffStrEqualsIgnCase(name, "herbstluftwm")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_HERBSTLUFTWM);
+ } else if (ffStrEqualsIgnCase(name, "icewm")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_ICEWM);
+ } else if (ffStrEqualsIgnCase(name, "dtwm")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_DTWM);
+ } else if (ffStrEqualsIgnCase(name, "fvwm")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_FVWM);
+ } else if (ffStrEqualsIgnCase(name, "ctwm")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_CTWM);
+ } else if (ffStrEqualsIgnCase(name, "hyprland")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_HYPRLAND);
+ } else if (ffStrEqualsIgnCase(name, "ratpoison")) {
+ ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_RATPOISON);
+ }
+}
+
+static void applyNameIfWM(FFDisplayServerResult* result, const char* processName) {
+ applyPrettyNameIfWM(result, processName);
+ if (result->wmPrettyName.length > 0) {
+ ffStrbufSetS(&result->wmProcessName, processName);
+ }
+}
+
+static void applyBetterWM(FFDisplayServerResult* result, const char* processName) {
+ if (!ffStrSet(processName)) {
+ return;
+ }
+
+ ffStrbufSetS(&result->wmProcessName, processName);
+
+ // If it is a known wm, this will set the pretty name
+ applyPrettyNameIfWM(result, processName);
+
+ // If it isn't a known wm, set the pretty name to the process name
+ if (result->wmPrettyName.length == 0) {
+ ffStrbufAppend(&result->wmPrettyName, &result->wmProcessName);
+ }
+}
+
+static void applyPrettyNameIfDE(FFDisplayServerResult* result, const char* name) {
+ if (!ffStrSet(name)) {
+ return;
+ }
+
+ else if (
+ ffStrEqualsIgnCase(name, "KDE") ||
+ ffStrEqualsIgnCase(name, "plasma") ||
+ ffStrEqualsIgnCase(name, "plasmashell") ||
+ ffStrEqualsIgnCase(name, "plasmawayland")) {
+ ffStrbufSetStatic(&result->deProcessName, "plasmashell");
+ ffStrbufSetStatic(&result->dePrettyName, FF_DE_PRETTY_PLASMA);
+ applyBetterWM(result, getenv("KDEWM"));
+ }
+
+ else if (
+ ffStrEqualsIgnCase(name, "GNOME") ||
+ ffStrEqualsIgnCase(name, "ubuntu:GNOME") ||
+ ffStrEqualsIgnCase(name, "ubuntu") ||
+ ffStrEqualsIgnCase(name, "gnome-shell")) {
+ ffStrbufSetStatic(&result->deProcessName, "gnome-shell");
+ const char* sessionMode = getenv("GNOME_SHELL_SESSION_MODE");
+ if (sessionMode && ffStrEquals(sessionMode, "classic")) {
+ ffStrbufSetStatic(&result->dePrettyName, FF_DE_PRETTY_GNOME_CLASSIC);
+ } else {
+ ffStrbufSetStatic(&result->dePrettyName, FF_DE_PRETTY_GNOME);
+ }
+ }
+
+ else if (
+ ffStrEqualsIgnCase(name, "X-Cinnamon") ||
+ ffStrEqualsIgnCase(name, "Cinnamon")) {
+ ffStrbufSetS(&result->deProcessName, "cinnamon");
+ ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_CINNAMON);
+ }
+
+ else if (
+ ffStrEqualsIgnCase(name, "XFCE") ||
+ ffStrEqualsIgnCase(name, "X-XFCE") ||
+ ffStrEqualsIgnCase(name, "XFCE4") ||
+ ffStrEqualsIgnCase(name, "X-XFCE4") ||
+ ffStrEqualsIgnCase(name, "xfce4-session")) {
+ ffStrbufSetS(&result->deProcessName, "xfce4-session");
+ ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_XFCE4);
+ }
+
+ else if (
+ ffStrEqualsIgnCase(name, "MATE") ||
+ ffStrEqualsIgnCase(name, "X-MATE") ||
+ ffStrEqualsIgnCase(name, "mate-session")) {
+ ffStrbufSetS(&result->deProcessName, "mate-session");
+ ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_MATE);
+ }
+
+ else if (
+ ffStrEqualsIgnCase(name, "LXQt") ||
+ ffStrEqualsIgnCase(name, "X-LXQt") ||
+ ffStrEqualsIgnCase(name, "lxqt-session")) {
+ ffStrbufSetS(&result->deProcessName, "lxqt-session");
+ ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_LXQT);
+ if (result->wmProcessName.length == 0) {
+ FF_STRBUF_AUTO_DESTROY wmProcessNameBuffer = ffStrbufCreate();
+ ffParsePropFileConfig("lxqt/session.conf", "window_manager =", &wmProcessNameBuffer);
+ applyBetterWM(result, wmProcessNameBuffer.chars);
+ }
+ }
+
+ else if (
+ ffStrEqualsIgnCase(name, "Budgie") ||
+ ffStrEqualsIgnCase(name, "X-Budgie") ||
+ ffStrEqualsIgnCase(name, "budgie-desktop") ||
+ ffStrEqualsIgnCase(name, "Budgie:GNOME")) {
+ ffStrbufSetS(&result->deProcessName, "budgie-desktop");
+ ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_BUDGIE);
+ }
+
+ else if (
+ ffStrEqualsIgnCase(name, "dtsession")) {
+ ffStrbufSetS(&result->deProcessName, "dtsession");
+ ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_CDE);
+ }
+
+ else if (
+ ffStrEqualsIgnCase(name, "ukui-session")) {
+ ffStrbufSetS(&result->deProcessName, "ukui-session");
+ ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_UKUI);
+ }
+
+ else if (
+ ffStrStartsWithIgnCase(name, "Unity:Unity")) {
+ ffStrbufSetS(&result->deProcessName, "unity-session");
+ ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_UNITY);
+ }
+
+ else if (ffStrEqualsIgnCase(name, "Enlightenment")) {
+ ffStrbufSetS(&result->deProcessName, "enlightenment_start");
+ ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_ENLIGHTENMENT);
+ }
+}
+
+static const char* getFromProcesses(FFDisplayServerResult* result) {
+ uint32_t userId = instance.state.platform.uid;
+
+#if __FreeBSD__
+ #ifdef __DragonFly__
+ #define ki_comm kp_comm
+ #endif
+
+ int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, (int) userId };
+ size_t length = 0;
+
+ if (sysctl(request, ARRAY_SIZE(request), NULL, &length, NULL, 0) != 0) {
+ return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, NULL) failed";
+ }
+
+ FF_AUTO_FREE struct kinfo_proc* procs = (struct kinfo_proc*) malloc(length);
+ if (sysctl(request, ARRAY_SIZE(request), procs, &length, NULL, 0) != 0) {
+ return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed";
+ }
+
+ length /= sizeof(*procs);
+
+ for (struct kinfo_proc* proc = procs; proc < procs + length; ++proc) {
+ if (result->dePrettyName.length == 0) {
+ applyPrettyNameIfDE(result, proc->ki_comm);
+ }
+
+ if (result->wmPrettyName.length == 0) {
+ applyNameIfWM(result, proc->ki_comm);
+ }
+
+ if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
+ break;
+ }
+ }
+#elif __OpenBSD__
+ kvm_t* kd = kvm_open(NULL, NULL, NULL, KVM_NO_FILES, NULL);
+ int count = 0;
+ const struct kinfo_proc* proc = kvm_getprocs(kd, KERN_PROC_UID, (int) userId, sizeof(*proc), &count);
+ if (proc) {
+ for (int i = 0; i < count; ++i) {
+ if (result->dePrettyName.length == 0) {
+ applyPrettyNameIfDE(result, proc[i].p_comm);
+ }
+
+ if (result->wmPrettyName.length == 0) {
+ applyNameIfWM(result, proc[i].p_comm);
+ }
+
+ if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
+ break;
+ }
+ }
+ }
+ kvm_close(kd);
+#elif __sun
+ FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc");
+ if (procdir == NULL) {
+ return "opendir(\"/proc\") failed";
+ }
+
+ FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
+ ffStrbufAppendS(&procPath, "/proc/");
+
+ uint32_t procPathLength = procPath.length;
+
+ struct dirent* dirent;
+ while ((dirent = readdir(procdir)) != NULL) {
+ if (!ffCharIsDigit(dirent->d_name[0])) {
+ continue;
+ }
+
+ ffStrbufAppendS(&procPath, dirent->d_name);
+ ffStrbufAppendS(&procPath, "/psinfo");
+ psinfo_t proc;
+ if (ffReadFileData(procPath.chars, sizeof(proc), &proc) == sizeof(proc)) {
+ ffStrbufSubstrBefore(&procPath, procPathLength);
+
+ if (proc.pr_uid != userId) {
+ continue;
+ }
+
+ if (result->dePrettyName.length == 0) {
+ applyPrettyNameIfDE(result, proc.pr_fname);
+ }
+
+ if (result->wmPrettyName.length == 0) {
+ applyNameIfWM(result, proc.pr_fname);
+ }
+
+ if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
+ break;
+ }
+ }
+ }
+#elif __linux__ || __GNU__
+ FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc");
+ if (procdir == NULL) {
+ return "opendir(\"/proc\") failed";
+ }
+
+ FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
+ ffStrbufAppendS(&procPath, "/proc/");
+
+ uint32_t procPathLength = procPath.length;
+
+ FF_STRBUF_AUTO_DESTROY loginuid = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY processName = ffStrbufCreateA(256); // Some processes have large command lines (looking at you chrome)
+
+ struct dirent* dirent;
+ while ((dirent = readdir(procdir)) != NULL) {
+ // Match only folders starting with a number (the pid folders)
+ if (dirent->d_type != DT_DIR || !ffCharIsDigit(dirent->d_name[0])) {
+ continue;
+ }
+
+ ffStrbufAppendS(&procPath, dirent->d_name);
+ uint32_t procFolderPathLength = procPath.length;
+
+ // Don't check for processes not owned by the current user.
+ ffStrbufAppendS(&procPath, "/loginuid");
+ ffReadFileBuffer(procPath.chars, &loginuid);
+ if (ffStrbufToUInt(&loginuid, (uint64_t) -1) != userId) {
+ ffStrbufSubstrBefore(&procPath, procPathLength);
+ continue;
+ }
+
+ ffStrbufSubstrBefore(&procPath, procFolderPathLength);
+
+ // We check the cmdline for the process name, because it is not trimmed.
+ ffStrbufAppendS(&procPath, "/cmdline");
+ ffReadFileBuffer(procPath.chars, &processName);
+ ffStrbufTrimRightSpace(&processName);
+ ffStrbufSubstrBeforeFirstC(&processName, '\0'); // Trim the arguments
+ ffStrbufSubstrAfterLastC(&processName, '/');
+
+ ffStrbufSubstrBefore(&procPath, procPathLength);
+
+ if (result->dePrettyName.length == 0) {
+ applyPrettyNameIfDE(result, processName.chars);
+ }
+
+ if (result->wmPrettyName.length == 0) {
+ applyNameIfWM(result, processName.chars);
+ }
+
+ if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
+ break;
+ }
+ }
+#elif __NetBSD__
+ int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_UID, (int) userId, sizeof(struct kinfo_proc2), INT_MAX };
+
+ size_t size = 0;
+ if (sysctl(request, ARRAY_SIZE(request), NULL, &size, NULL, 0) != 0) {
+ return "sysctl(KERN_PROC_UID, NULL) failed";
+ }
+
+ FF_AUTO_FREE struct kinfo_proc2* procs = malloc(size);
+
+ if (sysctl(request, ARRAY_SIZE(request), procs, &size, NULL, 0) != 0) {
+ return "sysctl(KERN_PROC_UID, procs) failed";
+ }
+
+ for (struct kinfo_proc2* proc = procs; proc < procs + (size / sizeof(struct kinfo_proc2)); proc++) {
+ if (result->dePrettyName.length == 0) {
+ applyPrettyNameIfDE(result, proc->p_comm);
+ }
+
+ if (result->wmPrettyName.length == 0) {
+ applyNameIfWM(result, proc->p_comm);
+ }
+
+ if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
+ break;
+ }
+ }
+#endif
+
+ return NULL;
+}
+
+void ffdsDetectWMDE(FFDisplayServerResult* result) {
+#if __ANDROID__
+ if (ffStrbufIgnCaseEqualS(&result->wmProtocolName, FF_WM_PROTOCOL_SURFACEFLINGER)) {
+ return; // Only supported when connected to X11
+ }
+#endif
+
+ const char* env = parseEnv();
+
+ if (result->wmProcessName.length > 0) {
+ // If we found the processName via display server, use it.
+ // This will set the pretty name if it is a known WM, otherwise the prettyName to the processName
+ applyPrettyNameIfWM(result, result->wmProcessName.chars);
+ if (result->wmPrettyName.length == 0) {
+ ffStrbufSet(&result->wmPrettyName, &result->wmProcessName);
+ }
+ } else {
+ // if env is a known WM, use it
+ applyNameIfWM(result, env);
+ }
+
+ // Connecting to a display server only gives WM results, not DE results.
+ // If we find it in the environment, use that.
+ applyPrettyNameIfDE(result, env);
+
+ // If WM was found by connection to the sever, and DE in the environment, we can return
+ // This way we never call getFromProcDir(), which has slow initialization time
+ if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
+ return;
+ }
+
+ // Get missing WM / DE from processes.
+ getFromProcesses(result);
+
+ // Return if both wm and de are set, or if env doesn't contain anything
+ if (
+ (result->wmPrettyName.length > 0 && result->dePrettyName.length > 0) ||
+ !ffStrSet(env)) {
+ return;
+ }
+
+ // If nothing is set, use env as WM
+ else if (result->wmPrettyName.length == 0 && result->dePrettyName.length == 0) {
+ ffStrbufSetS(&result->wmProcessName, env);
+ ffStrbufSetS(&result->wmPrettyName, env);
+ }
+
+ // If only WM is not set, and DE doesn't equal env, use env as WM
+ else if (
+ result->wmPrettyName.length == 0 &&
+ ffStrbufIgnCaseCompS(&result->deProcessName, env) != 0 &&
+ ffStrbufIgnCaseCompS(&result->dePrettyName, env) != 0) {
+ ffStrbufSetS(&result->wmProcessName, env);
+ ffStrbufSetS(&result->wmPrettyName, env);
+ }
+
+ // If only DE is not set, and WM doesn't equal env, use env as DE
+ else if (
+ result->dePrettyName.length == 0 &&
+ ffStrbufIgnCaseCompS(&result->wmProcessName, env) != 0 &&
+ ffStrbufIgnCaseCompS(&result->wmPrettyName, env) != 0) {
+ ffStrbufSetS(&result->deProcessName, env);
+ ffStrbufSetS(&result->dePrettyName, env);
+ }
+}