如何通过libnotify-dev包优雅实现以root运行的C++特权守护进程向所有桌面用户发送通知
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:
- Identify all active desktop users (those with a running session)
- For each user, spawn a child process that drops privileges to their UID
- Set the correct environment variables to connect to their session DBus
- 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>/busfor 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 (likegnome-shellorkwin) to getDBUS_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

