You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过libnotify-dev包优雅实现以root运行的C++特权守护进程向所有桌面用户发送通知

Sending Notifications to All Desktop Users from a Root C++ Daemon

Great question! The core challenge here is that root doesn’t have access to any user’s desktop session DBus bus—libnotify relies on this bus to deliver notifications to the desktop environment. To work around this, we need to:

  1. Identify all active desktop users (those with a running session)
  2. For each user, spawn a child process that drops privileges to their UID
  3. Set the correct environment variables to connect to their session DBus
  4. Use libnotify to send the notification from that low-privilege child process

Here’s a clean, secure implementation:

Step 1: Enumerate Active Users

We can leverage the /run/user directory (standard on systemd-based distros, which most modern Linux systems use) to find active users. Each directory here is named after the user’s UID and contains their session bus socket.

#include <vector>
#include <string>
#include <dirent.h>
#include <cstdlib>
#include <cstring>
#include <unistd.h>

std::vector<uid_t> get_active_users() {
    std::vector<uid_t> uids;
    DIR* run_user_dir = opendir("/run/user");
    
    if (!run_user_dir) {
        perror("Failed to open /run/user");
        return uids;
    }

    struct dirent* entry;
    while ((entry = readdir(run_user_dir)) != nullptr) {
        // Skip . and .. entries
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }

        // Parse directory name as UID
        char* endptr;
        uid_t uid = strtoul(entry->d_name, &endptr, 10);
        if (*endptr != '\0') {
            continue; // Not a valid UID
        }

        // Verify the session bus socket exists
        std::string bus_path = "/run/user/" + std::to_string(uid) + "/bus";
        if (access(bus_path.c_str(), F_OK) == 0) {
            uids.push_back(uid);
        }
    }

    closedir(run_user_dir);
    return uids;
}

Step 2: Send Notification as a Specific User

For each user, we fork a child process, drop privileges to their UID/GID, set the required environment variables, then use libnotify to send the notification.

#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <pwd.h>
#include <libnotify/notify.h>
#include <glib.h>

bool send_notification_as_user(uid_t uid, const std::string& title, const std::string& message) {
    pid_t pid = fork();
    if (pid == -1) {
        perror("Fork failed");
        return false;
    }

    if (pid == 0) {
        // Child process: Drop privileges to target user
        struct passwd pw;
        struct passwd* pw_ptr;
        char pw_buf[1024];

        if (getpwuid_r(uid, &pw, pw_buf, sizeof(pw_buf), &pw_ptr) != 0 || !pw_ptr) {
            perror("Failed to get user info");
            exit(EXIT_FAILURE);
        }

        // Set group ID first, then user ID (order matters for security!)
        if (setgid(pw.pw_gid) == -1) {
            perror("Failed to set group ID");
            exit(EXIT_FAILURE);
        }
        if (setuid(pw.pw_uid) == -1) {
            perror("Failed to set user ID");
            exit(EXIT_FAILURE);
        }

        // Set critical environment variables for DBus/libnotify
        std::string dbus_address = "unix:path=/run/user/" + std::to_string(uid) + "/bus";
        setenv("DBUS_SESSION_BUS_ADDRESS", dbus_address.c_str(), 1);
        setenv("XDG_RUNTIME_DIR", ("/run/user/" + std::to_string(uid)).c_str(), 1);

        // Initialize libnotify and send notification
        if (!notify_init("Root Daemon Notifier")) {
            fprintf(stderr, "Failed to initialize libnotify\n");
            exit(EXIT_FAILURE);
        }

        NotifyNotification* notification = notify_notification_new(
            title.c_str(),
            message.c_str(),
            nullptr // Optional: Path to an icon (e.g., "dialog-warning")
        );

        if (!notification) {
            fprintf(stderr, "Failed to create notification\n");
            notify_uninit();
            exit(EXIT_FAILURE);
        }

        GError* error = nullptr;
        if (!notify_notification_show(notification, &error)) {
            fprintf(stderr, "Failed to show notification: %s\n", error->message);
            g_error_free(error);
            g_object_unref(notification);
            notify_uninit();
            exit(EXIT_FAILURE);
        }

        // Cleanup
        g_object_unref(notification);
        notify_uninit();
        exit(EXIT_SUCCESS);
    } else {
        // Parent process: Wait for child to finish and check status
        int status;
        waitpid(pid, &status, 0);
        return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS;
    }
}

Step 3: Integrate into Your Daemon

Call these functions whenever you need to broadcast a notification to all active users:

int main() {
    // ... Your daemon initialization logic ...

    // Example: Send a system alert to all users
    std::vector<uid_t> active_users = get_active_users();
    for (uid_t uid : active_users) {
        bool success = send_notification_as_user(
            uid,
            "System Alert",
            "Your root-privileged daemon needs your attention!"
        );
        if (!success) {
            std::cerr << "Failed to send notification to UID " << uid << "\n";
        }
    }

    // ... Rest of your daemon code ...
    return 0;
}

Compilation Instructions

Link against libnotify and glib-2.0 using pkg-config to handle dependencies automatically:

g++ -o daemon_notify daemon_notify.cpp `pkg-config --cflags --libs libnotify glib-2.0`

Key Notes

  • Systemd Compatibility: This implementation relies on /run/user/<uid>/bus for the session bus, which is standard on systemd systems. For non-systemd distros, you’d need to parse the user’s environment from a running desktop process (like gnome-shell or kwin) to get DBUS_SESSION_BUS_ADDRESS.
  • Security: We drop privileges immediately in the child process—this ensures the notification code runs with minimal permissions, reducing attack surface.
  • Cross-Protocol Support: Libnotify works with both Wayland and X11 as long as the DBus session is correctly configured, so no extra handling is needed for display protocols.
  • Error Handling: The code includes basic error checking, but you can expand it to handle edge cases like users logging out mid-notification or missing libnotify on a user’s system.

内容的提问来源于stack exchange,提问作者vkg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 17:09:06