From 2876a45a85652ff3a505dd6d1caac11367fb5d68 Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Fri, 29 Aug 2025 02:33:55 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=BB=D1=83=D1=87=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D1=8F,=20=D0=BD=D0=BE=20=D0=BD=D0=B5=20100%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/app.d | 16 ++++- source/dfanotify.d | 5 ++ source/event_actor.d | 139 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 source/event_actor.d diff --git a/source/app.d b/source/app.d index fffdac5..6b67df9 100644 --- a/source/app.d +++ b/source/app.d @@ -2,6 +2,7 @@ module app; import dfanotify; // твой модуль сверху import depoll; // новый модуль для epoll +import event_actor; import core.sys.posix.fcntl : O_RDONLY, O_LARGEFILE; import core.stdc.errno : errno; @@ -277,7 +278,16 @@ void main(string[] args) foreach (ev; events) { auto pname = eventProcessName(ev.pid); - writeln("pid = ", ev.pid, "\tproc = ", pname, "\tmask = ", maskToStr(ev.mask)); + // writeln("pid = ", ev.pid, "\tproc = ", pname, "\tmask = ", maskToStr(ev.mask)); + writefln("[%s] pid = %s proc = %s mask = %s", + (tag == TAG_PRE) ? "PRE" : "NOTIF", + ev.pid, pname, + maskToStr(ev.mask) + ); + auto actor = eventActorFromPid(ev.pid); + writeln("user=", actor.user, " uid=", actor.uid, + " ruid=", actor.ruid, "(", actor.ruser, ")", + " euid=", actor.euid, "(", actor.euser, ")"); if (tag == TAG_PRE && (ev.isOpenPerm || ev.isAccessPerm)) { @@ -329,6 +339,10 @@ void main(string[] args) // snag --config ... create --comment "CLOSE_WRITE - " ... writefln(" -> HANDLE CLOSE_WRITE for: %s", ev.name); } + else if (ev.isMoved) + { + writefln(" -> HANDLE MOVED for: %s", ev.name); + } else if (ev.isModify) { writefln(" -> HANDLE MODIFY for: %s", ev.name); diff --git a/source/dfanotify.d b/source/dfanotify.d index 8144d08..b4d4d30 100644 --- a/source/dfanotify.d +++ b/source/dfanotify.d @@ -83,6 +83,11 @@ class FanotifyEvent return (mask & FAN_ACCESS) != 0; } + bool isMoved() const + { + return (mask & FAN_MOVED_FROM) != 0; + } + bool isCreate() const { return (mask & FAN_CREATE) != 0; diff --git a/source/event_actor.d b/source/event_actor.d new file mode 100644 index 0000000..5ac27de --- /dev/null +++ b/source/event_actor.d @@ -0,0 +1,139 @@ +module event_actor; + +import core.sys.posix.pwd : passwd, getpwuid_r; +import core.sys.posix.sys.types : uid_t; +import core.stdc.stdlib : malloc, free; +import core.stdc.string : strlen; +import std.file : readText, exists; +import std.string : splitLines, strip; +import std.conv : to; + +// для токенизации по пробельным +import std.ascii : isWhite; +import std.algorithm.iteration : splitter; +import std.array : array; + +struct EventActor +{ + uid_t uid; // выбранный UID (loginuid если есть, иначе ruid) + string user; // имя пользователя для uid (может быть пустым) + uid_t ruid, euid, suid, fsuid; + string ruser, euser, suser, fsuser; + bool hasLoginuid; + uid_t loginuid; + string loginuser; +} + +private string userName(uid_t uid) +{ + enum BUF = 4096; + auto buf = cast(char*) malloc(BUF); + scope (exit) + if (buf) + free(buf); + passwd pwd; + passwd* outp; + auto rc = getpwuid_r(uid, &pwd, buf, BUF, &outp); + if (rc == 0 && outp !is null) + { + return pwd.pw_name[0 .. strlen(pwd.pw_name)].idup; + } + return ""; +} + +/// Парсит строку вида: "Uid:\t\t\t\t" +private bool parseUidLine(string line, out uid_t r, out uid_t e, out uid_t s, out uid_t f) +{ + // разобьём по любым пробельным символам + auto toks = line.splitter!isWhite.array; // ["Uid:", "1000", "1000", "1000", "1000"] + if (toks.length >= 5 && toks[0] == "Uid:") + { + try + { + r = toks[1].to!uid_t; + e = toks[2].to!uid_t; + s = toks[3].to!uid_t; + f = toks[4].to!uid_t; + return true; + } + catch (Exception e) + { + } + } + return false; +} + +/// Главная функция: кто инициировал событие (по PID из fanotify_event_metadata) +EventActor eventActorFromPid(int pid) +{ + EventActor a; + if (pid <= 0) + return a; + + const base = "/proc/" ~ pid.to!string ~ "/"; + const statusPath = base ~ "status"; + + if (exists(statusPath)) + { + try + { + auto txt = readText(statusPath); + uid_t r = 0, e = 0, s = 0, f = 0; + foreach (line; txt.splitLines()) + { + // ищем строку "Uid:" + if (line.length >= 4 && line[0 .. 4] == "Uid:") + { + if (parseUidLine(line, r, e, s, f)) + { + a.ruid = r; + a.euid = e; + a.suid = s; + a.fsuid = f; + a.ruser = userName(r); + a.euser = userName(e); + a.suser = userName(s); + a.fsuser = userName(f); + } + break; + } + } + } + catch (Exception e) + { + } + } + + // loginuid — владелец сессии (если audit включён) + const loginPath = base ~ "loginuid"; + if (exists(loginPath)) + { + try + { + auto s = readText(loginPath).strip; + if (s.length) + { + a.loginuid = s.to!uid_t; + a.loginuser = userName(a.loginuid); + a.hasLoginuid = true; + } + } + catch (Exception e) + { + } + } + + // выбираем «итогового» пользователя + if (a.hasLoginuid) + { + a.uid = a.loginuid; + a.user = a.loginuser.length ? a.loginuser : ("uid:" ~ a.loginuid.to!string); + } + else + { + a.uid = a.ruid; + a.user = a.ruser.length ? a.ruser : ("uid:" ~ a.ruid.to!string); + } + + return a; +}