diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/pam_xdg.cpp | 272 |
1 files changed, 208 insertions, 64 deletions
diff --git a/src/pam_xdg.cpp b/src/pam_xdg.cpp index 353f443..b397024 100644 --- a/src/pam_xdg.cpp +++ b/src/pam_xdg.cpp @@ -1,83 +1,205 @@ -#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> +#include <stdexcept> +#include <string> +#include <string_view> -const std::filesystem::path DB_PATH = "/run/pam_xdg.db"; -const std::filesystem::path RUNTIME_DIRECTORY = "/run/user/"; +#include <fcntl.h> +#include <pwd.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <security/pam_modules.h> +#include <sqlite3.h> 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))); +constexpr const std::string_view USER_DIRECTORY = "/run/user"; +constexpr const std::string_view DB_PATH = "/run/pam_xdg.db"; + +void open_db(sqlite3 **db, const std::filesystem::path &path, int flags) { + auto rc = sqlite3_open_v2(path.c_str(), db, flags, nullptr); + + if (rc != SQLITE_OK) { + throw std::runtime_error(std::string("failed to open database")); } +} + +void init_db(sqlite3 *db) { + auto rc = sqlite3_exec( + db, "CREATE TABLE IF NOT EXISTS SESSIONS(USERNAME TEXT, LOGINS INT)", + nullptr, nullptr, nullptr); - if (chown(path.c_str(), owner, owner) != 0) { + if (rc != SQLITE_OK) { throw std::runtime_error( - std::format("failed to chown directory {}", path.c_str())); + std::string("failed to init database: {}", sqlite3_errmsg(db))); } } -void create_table(SQLite::Database &db) { - db.exec("CREATE TABLE IF NOT EXISTS SESSIONS(USERNAME STRING, LOGINS INT)"); -} +void init_session(sqlite3 *db, std::string_view username) { + const auto *query = + "INSERT OR IGNORE INTO SESSIONS(USERNAME, LOGINS) VALUES(?, 0)"; + sqlite3_stmt *stmt; + int rc; -void create_session(SQLite::Database &db, const std::string &username) { - SQLite::Statement stmt( - db, "INSERT OR IGNORE INTO SESSIONS(USERNAME, LOGINS) VALUES(?, 0)"); + rc = sqlite3_prepare_v2(db, query, -1, &stmt, NULL); - stmt.bind(1, username); + if (rc != SQLITE_OK) { + throw std::runtime_error( + std::format("failed to prepare stmt: {}", sqlite3_errmsg(db))); + } - stmt.exec(); + rc = sqlite3_bind_text(stmt, 1, username.data(), username.length(), nullptr); + + if (rc != SQLITE_OK) { + sqlite3_finalize(stmt); + throw std::runtime_error(std::string("failed to bind text to stmt")); + } + + rc = sqlite3_step(stmt); + + if (rc != SQLITE_DONE) { + sqlite3_finalize(stmt); + throw std::runtime_error(std::string("failed to step stmt")); + } + + sqlite3_finalize(stmt); } -void login(SQLite::Database &db, const std::string &username) { - SQLite::Statement stmt( - db, "UPDATE SESSIONS SET LOGINS=LOGINS+1 WHERE USERNAME=?"); +void record_login(sqlite3 *db, std::string_view username) { + const auto *query = "UPDATE SESSIONS SET LOGINS=LOGINS+1 WHERE USERNAME=?"; + sqlite3_stmt *stmt; + int rc; + + rc = sqlite3_prepare_v2(db, query, -1, &stmt, nullptr); - stmt.bind(1, username); + if (rc != SQLITE_OK) { + throw std::runtime_error( + std::string("failed to prepare stmt: {}", sqlite3_errmsg(db))); + } + + rc = sqlite3_bind_text(stmt, 1, username.data(), username.length(), nullptr); + + if (rc != SQLITE_OK) { + sqlite3_finalize(stmt); + throw std::runtime_error(std::string("failed to bind text")); + } - stmt.exec(); + rc = sqlite3_step(stmt); + + if (rc != SQLITE_DONE) { + sqlite3_finalize(stmt); + throw std::runtime_error(std::string("failed to step stmt")); + } + + sqlite3_finalize(stmt); } -void logout(SQLite::Database &db, const std::string &username) { - SQLite::Statement stmt( - db, "UPDATE SESSIONS SET LOGINS=LOGINS-1 WHERE USERNAME=?"); +void record_logout(sqlite3 *db, std::string_view username) { + const auto *query = "UPDATE SESSIONS SET LOGINS=LOGINS-1 WHERE USERNAME=?"; + sqlite3_stmt *stmt; + int rc; + + rc = sqlite3_prepare_v2(db, query, -1, &stmt, nullptr); + + if (rc != SQLITE_OK) { + throw std::runtime_error( + std::string("failed to prepare stmt: {}", sqlite3_errmsg(db))); + } + + rc = sqlite3_bind_text(stmt, 1, username.data(), username.length(), nullptr); + + if (rc != SQLITE_OK) { + sqlite3_finalize(stmt); + throw std::runtime_error(std::string("failed to bind text")); + } + + rc = sqlite3_step(stmt); - stmt.bind(1, username); + if (rc != SQLITE_DONE) { + sqlite3_finalize(stmt); + throw std::runtime_error(std::string("failed to step stmt")); + } - stmt.exec(); + sqlite3_finalize(stmt); } -bool is_logged_in(SQLite::Database &db, const std::string &username) { - SQLite::Statement stmt(db, "SELECT LOGINS FROM SESSIONS WHERE USERNAME=?"); +bool is_logged_in(sqlite3 *db, std::string_view username) { + const auto *query = "SELECT LOGINS FROM SESSIONS WHERE USERNAME=?"; + sqlite3_stmt *stmt; + int rc; + + rc = sqlite3_prepare_v2(db, query, -1, &stmt, nullptr); + + if (rc != SQLITE_OK) { + throw std::runtime_error( + std::format("failed to prepare stmt: {}", sqlite3_errmsg(db))); + } + + rc = sqlite3_bind_text(stmt, 1, username.data(), username.length(), nullptr); + + if (rc != SQLITE_OK) { + sqlite3_finalize(stmt); + throw std::runtime_error(std::string("failed to bind text")); + } + + rc = sqlite3_step(stmt); - stmt.bind(1, username); + if (rc != SQLITE_ROW) { + sqlite3_finalize(stmt); + throw std::runtime_error(std::string("failed to step stmt")); + } - stmt.executeStep(); + int logins = sqlite3_column_int(stmt, 0); - int logins = stmt.getColumn(0); + sqlite3_finalize(stmt); return (logins > 0); } + +void touch(const std::filesystem::path &path, int mode) { + auto fd = creat(path.c_str(), mode); + + if (fd == -1) { + throw std::runtime_error(std::format("failed to touch file: {} {}", + path.c_str(), std::strerror(errno))); + } + + close(fd); +} + +void create_directory(const std::filesystem::path &path, std::uint16_t owner, + int mode) { + int rc; + + rc = mkdir(path.c_str(), mode); + + if (rc == -1) { + throw std::runtime_error(std::format("failed to mkdir: {} {}", path.c_str(), + std::strerror(errno))); + } + + rc = chown(path.c_str(), owner, owner); + + if (rc == -1) { + throw std::runtime_error(std::format("failed to chown: {} {}", path.c_str(), + std::strerror(errno))); + } +} } // namespace pam_xdg -extern "C" int pam_sm_open_session(pam_handle_t *pam, [[gnu::unused]] int flags, +extern "C" int pam_sm_open_session(pam_handle_t *pamh, + [[gnu::unused]] int flags, [[gnu::unused]] int argc, [[gnu::unused]] const char **argv) { const void *data; - pam_get_item(pam, PAM_USER, &data); + if (pam_get_item(pamh, PAM_USER, &data) != PAM_SUCCESS) { + std::println(stderr, "failed to get pam item PAM_USER"); + return PAM_SESSION_ERR; + } auto *username = static_cast<const char *>(data); if (std::string_view(username) == "root") { @@ -87,32 +209,49 @@ extern "C" int pam_sm_open_session(pam_handle_t *pam, [[gnu::unused]] int flags, auto passwd = getpwnam(username); auto uid = passwd->pw_uid; - auto user_directory = RUNTIME_DIRECTORY / std::format("{}", uid); + auto xdg_runtime_dir = + std::filesystem::path(pam_xdg::USER_DIRECTORY) / std::format("{}", uid); + + sqlite3 *db; try { - SQLite::Database db(DB_PATH, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); + if (!std::filesystem::exists(pam_xdg::DB_PATH)) { + pam_xdg::touch(pam_xdg::DB_PATH, 0600); - pam_xdg::create_table(db); - pam_xdg::create_session(db, username); + pam_xdg::open_db(&db, pam_xdg::DB_PATH, + SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE); + + pam_xdg::init_db(db); + } else { + pam_xdg::open_db(&db, pam_xdg::DB_PATH, SQLITE_OPEN_READWRITE); + } + + pam_xdg::init_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); + if (!std::filesystem::exists(pam_xdg::USER_DIRECTORY)) { + pam_xdg::create_directory(pam_xdg::USER_DIRECTORY, 0, 0755); + } + if (!std::filesystem::is_directory(pam_xdg::USER_DIRECTORY)) { + throw std::runtime_error( + std::format("{} is not a directory", pam_xdg::USER_DIRECTORY)); } - pam_xdg::create_directory(user_directory, 0700, uid); + pam_xdg::create_directory(std::filesystem::path(pam_xdg::USER_DIRECTORY) / + std::format("{}", uid), + uid, 0700); } - pam_xdg::login(db, username); + pam_xdg::record_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")); + auto envvar = std::format("XDG_RUNTIME_DIR={}", xdg_runtime_dir.c_str()); + if (pam_putenv(pamh, envvar.c_str()) != PAM_SUCCESS) { + throw std::runtime_error(std::format("failed to putenv: {}", envvar)); } - } catch (std::exception &e) { - std::print("{}", e.what()); + + } catch (const std::exception &e) { + std::println(stderr, "{}", e.what()); + return PAM_SESSION_ERR; } @@ -125,7 +264,10 @@ extern "C" int pam_sm_close_session(pam_handle_t *pam, [[gnu::unused]] const char **argv) { const void *data; - pam_get_item(pam, PAM_USER, &data); + if (pam_get_item(pam, PAM_USER, &data) != PAM_SUCCESS) { + std::println(stderr, "failed to get pam item PAM_USER"); + return PAM_SESSION_ERR; + } auto *username = static_cast<const char *>(data); if (std::string_view(username) == "root") { @@ -135,20 +277,22 @@ extern "C" int pam_sm_close_session(pam_handle_t *pam, auto passwd = getpwnam(username); auto uid = passwd->pw_uid; - auto user_directory = std::filesystem::path( - std::filesystem::path(RUNTIME_DIRECTORY) / std::format("{}", uid)); + auto xdg_runtime_dir = + std::filesystem::path(pam_xdg::USER_DIRECTORY) / std::format("{}", uid); + + sqlite3 *db; try { - SQLite::Database db(DB_PATH, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); + pam_xdg::open_db(&db, pam_xdg::DB_PATH, SQLITE_OPEN_READWRITE); - pam_xdg::logout(db, username); + pam_xdg::record_logout(db, username); if (!pam_xdg::is_logged_in(db, username)) { - std::filesystem::remove_all(user_directory); + std::filesystem::remove_all(xdg_runtime_dir); } + } catch (const std::exception &e) { + std::println(stderr, "{}", e.what()); - } catch (std::exception &e) { - std::print("{}", e.what()); return PAM_SESSION_ERR; } |