]> jturnerusa.dev Git - pam_xdg/commitdiff
init master 0.0.1
authorJohn Turner <jturner.usa@gmail.com>
Thu, 24 Jul 2025 07:58:01 +0000 (03:58 -0400)
committerJohn Turner <jturner.usa@gmail.com>
Thu, 24 Jul 2025 13:56:04 +0000 (09:56 -0400)
.dir-locals.el [new file with mode: 0644]
meson.build [new file with mode: 0644]
src/pam_xdg.cpp [new file with mode: 0644]

diff --git a/.dir-locals.el b/.dir-locals.el
new file mode 100644 (file)
index 0000000..a08f21b
--- /dev/null
@@ -0,0 +1,13 @@
+((c++-ts-mode
+  . ((fmt-executable . "clang-format")
+     (eval . (setq-local fmt-args `("--assume-filename" ,(buffer-file-name))))
+     (eval . (add-hook 'before-save-hook 'fmt-current-buffer nil t))
+     (eval . (add-to-list 'eglot-server-programs '(c++-mode . ("clangd" "-header-insertion=never" "-clang-tidy"))))
+     (eval . (eglot-ensure))
+     (eval . (add-hook 'eglot-managed-mode-hook (lambda ()
+                                                  (eglot-inlay-hints-mode -1))))
+     (eval . (company-mode 1))))
+ (meson-mode
+  . ((fmt-executable . "meson")
+     (fmt-args . ("format" "-"))
+     (eval . (add-hook 'before-save-hook 'fmt-current-buffer nil t)))))
diff --git a/meson.build b/meson.build
new file mode 100644 (file)
index 0000000..2960abd
--- /dev/null
@@ -0,0 +1,24 @@
+project(
+    'pam_xdg',
+    'cpp',
+    version: '0.0.1',
+    meson_version: '>=1.4.0',
+    default_options: ['warning_level=3', 'cpp_std=c++23'],
+)
+
+pam = dependency('pam')
+sqlite3 = dependency('sqlite3')
+sqlitecpp = dependency('sqlitecpp')
+
+sources = files('src/pam_xdg.cpp')
+
+libdir = get_option('libdir')
+
+pam_xdg = shared_library(
+    'pam_xdg',
+    sources,
+    dependencies: [pam, sqlite3, sqlitecpp],
+    install: true,
+    install_dir: libdir / 'security',
+    name_prefix: '',
+)
diff --git a/src/pam_xdg.cpp b/src/pam_xdg.cpp
new file mode 100644 (file)
index 0000000..de2d41f
--- /dev/null
@@ -0,0 +1,156 @@
+#include <SQLiteCpp/SQLiteCpp.h>
+#include <pwd.h>
+#include <security/pam_modules.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <cerrno>
+#include <cstring>
+#include <filesystem>
+#include <format>
+#include <print>
+
+const std::filesystem::path DB_PATH = "/run/xdg_pam.db";
+const std::filesystem::path RUNTIME_DIRECTORY = "/run/user/";
+
+namespace pam_xdg {
+
+void create_directory(const std::filesystem::path &path, mode_t chmod,
+                      int owner) {
+  if (mkdir(path.c_str(), chmod) != 0) {
+    throw std::runtime_error(std::format("failed to create directory {}: {}",
+                                         path.c_str(), std::strerror(errno)));
+  }
+
+  if (chown(path.c_str(), owner, owner) != 0) {
+    throw std::runtime_error(
+        std::format("failed to chown directory {}", path.c_str()));
+  }
+}
+
+void create_table(SQLite::Database &db) {
+  db.exec("CREATE TABLE IF NOT EXISTS SESSIONS(USERNAME STRING, LOGINS INT)");
+}
+
+void create_session(SQLite::Database &db, const std::string &username) {
+  SQLite::Statement stmt(
+      db, "INSERT OR IGNORE INTO SESSIONS(USERNAME, LOGINS) VALUES(?, 0)");
+
+  stmt.bind(1, username);
+
+  stmt.exec();
+}
+
+void login(SQLite::Database &db, const std::string &username) {
+  SQLite::Statement stmt(
+      db, "UPDATE SESSIONS SET LOGINS=LOGINS+1 WHERE USERNAME=?");
+
+  stmt.bind(1, username);
+
+  stmt.exec();
+}
+
+void logout(SQLite::Database &db, const std::string &username) {
+  SQLite::Statement stmt(
+      db, "UPDATE SESSIONS SET LOGINS=LOGINS-1 WHERE USERNAME=?");
+
+  stmt.bind(1, username);
+
+  stmt.exec();
+}
+
+bool is_logged_in(SQLite::Database &db, const std::string &username) {
+  SQLite::Statement stmt(db, "SELECT LOGINS FROM SESSIONS WHERE USERNAME=?");
+
+  stmt.bind(1, username);
+
+  stmt.executeStep();
+
+  int logins = stmt.getColumn(0);
+
+  return (logins > 0);
+}
+} // namespace pam_xdg
+
+extern "C" int pam_sm_open_session(pam_handle_t *pam, [[gnu::unused]] int flags,
+                                   [[gnu::unused]] int argc,
+                                   [[gnu::unused]] const char **argv) {
+
+  const void *data;
+  pam_get_item(pam, PAM_USER, &data);
+  auto *username = static_cast<const char *>(data);
+
+  if (std::string_view(username) == "root") {
+    return 0;
+  }
+
+  auto passwd = getpwnam(username);
+  auto uid = passwd->pw_uid;
+
+  auto user_directory = RUNTIME_DIRECTORY / std::format("{}", uid);
+
+  try {
+    SQLite::Database db(DB_PATH, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
+
+    pam_xdg::create_table(db);
+    pam_xdg::create_session(db, username);
+
+    if (!pam_xdg::is_logged_in(db, username)) {
+      if (!std::filesystem::exists(RUNTIME_DIRECTORY)) {
+        pam_xdg::create_directory(RUNTIME_DIRECTORY, 0755, 0);
+      }
+
+      pam_xdg::create_directory(user_directory, 0700, uid);
+    }
+
+    pam_xdg::login(db, username);
+
+    auto envvar = std::format("XDG_RUNTIME_DIR={}", user_directory.c_str());
+
+    if (pam_putenv(pam, envvar.c_str()) != PAM_SUCCESS) {
+      throw std::runtime_error(
+          std::string("failed to set XDG_RUNTIME_DIR environment variable"));
+    }
+  } catch (std::exception &e) {
+    std::print("{}", e.what());
+    return PAM_SESSION_ERR;
+  }
+
+  return PAM_SUCCESS;
+}
+
+extern "C" int pam_sm_close_session(pam_handle_t *pam,
+                                    [[gnu::unused]] int flags,
+                                    [[gnu::unused]] int argc,
+                                    [[gnu::unused]] const char **argv) {
+
+  const void *data;
+  pam_get_item(pam, PAM_USER, &data);
+  auto *username = static_cast<const char *>(data);
+
+  if (std::string_view(username) == "root") {
+    return 0;
+  }
+
+  auto passwd = getpwnam(username);
+  auto uid = passwd->pw_uid;
+
+  auto user_directory = std::filesystem::path(
+      std::filesystem::path(RUNTIME_DIRECTORY) / std::format("{}", uid));
+
+  try {
+    SQLite::Database db(DB_PATH, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
+
+    pam_xdg::logout(db, username);
+
+    if (!pam_xdg::is_logged_in(db, username)) {
+      std::filesystem::remove_all(user_directory);
+    }
+
+  } catch (std::exception &e) {
+    std::print("{}", e.what());
+    return PAM_SESSION_ERR;
+  }
+
+  return PAM_SUCCESS;
+}