summaryrefslogtreecommitdiff
path: root/src/pam_xdg.cpp
blob: 353f44315ccdb0e592ffde557d712f5941a66c52 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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/pam_xdg.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;
}