[Add] Implement user and group management.

This commit is contained in:
Ritchie Cunningham 2025-11-05 21:04:05 +00:00
parent 2e5ddcd2e6
commit d852d8449f
14 changed files with 251 additions and 9 deletions

View File

@ -0,0 +1,9 @@
-- /bin/chgrp - Change file group.
local group = arg[1]
local path = arg[2]
if not group or not path then
return "chgrp: missing operand"
end
return bettola.chgrp(context, group, path)

View File

@ -0,0 +1,9 @@
-- /bin/chown - Change file owner.
local owner = arg[1]
local path = arg[2]
if not owner or not path then
return "chown: missing operand"
end
return bettola.chown(context, owner, path)

View File

@ -16,6 +16,34 @@ local function format_permissions(perms)
return table.concat(rwx)
end
local function get_username_from_uid(uid)
local passwd_content = bettola.read_file(context, "/etc/passwd")
for line in passwd_content:gmatch("(^\n]+)") do
local parts = {}
for part in line:gmatch("([^:]+)") do
table.insert(parts,part)
end
if #parts >= 3 and tonumber(parts[3]) == uid then
return parts[1]
end
end
return tostring(uid)
end
local function get_groupname_from_gid(gid)
local group_content = bettola.read_file(context, "/etc/group")
for line in group_content:gmatch("([^\n]+)") do
local parts = {}
for part in line:gmatch("([^:]+)") do
table.insert(parts, part)
end
if #parts >= 3 and tonumber(parts[3]) == gid then
return parts[1]
end
end
return tostring(gid)
end
local function get_file_size(node)
if node.type == 0 then -- FILE_NODE.
return #node.content
@ -24,15 +52,34 @@ local function get_file_size(node)
end
end
-- Cache for UID/GID to name mappings.
local uid_to_name_cache = {}
local gid_to_name_cache = {}
local function get_cached_username(uid)
if not uid_to_name_cache[uid] then
uid_to_name_cache[uid] = get_username_from_uid(uid)
end
return uid_to_name_cache[uid]
end
local function get_cached_groupname(gid)
if not gid_to_name_cache[gid] then
gid_to_name_cache[gid] = get_groupname_from_gid(gid)
end
return gid_to_name_cache[gid]
end
local function ls_long_format(dir)
local output = {}
for name, node in pairs(dir.children) do
local line_type = (node.type == 1) and "d" or "-"
local perms = format_permissions(node.permissions)
local owner = node.owner_id
local group = node.group_id
local owner_name = get_cached_username(node.owner_id)
local group_name = get_cached_groupname(node.group_id)
local size = get_file_size(node)
table.insert(output, string.format("%s%s %d %d %5d %s", line_type, perms, owner, group, size, name))
table.insert(output, string.format("%s%s %s %s %5d %s", line_type, perms, owner_name,
group_name, size, name))
end
table.sort(output)
return table.concat(output, "\n")

View File

@ -0,0 +1,2 @@
-- /bin/whoami - Print the effective username of the current user.
return bettola.get_username(context)

View File

@ -71,8 +71,20 @@ bool DatabaseManager::create_player(const std::string& username, const std::stri
/* Create default subdirs. */
_vfs_repository->create_node(machine_id, &root_id, "home", DIR_NODE,
"", player_id, player_id, 0755);
_vfs_repository->create_node(machine_id, &root_id, "etc", DIR_NODE,
"", player_id, player_id, 0755);
long long etc_id = _vfs_repository->create_node(machine_id, &root_id, "etc", DIR_NODE,
"", 0, 0, 0755);
/* Create /etc/passwd and /etc/group. */
std::string passwd_content = "root:x:0:0\n";
passwd_content += username + ":x:" + std::to_string(player_id) + ":"
+ std::to_string(player_id) + "\n";
_vfs_repository->create_node(machine_id, &etc_id, "passwd", FILE_NODE,
passwd_content, 0, 0, 0644);
std::string group_content = "root:x:\n";
group_content += username + ":x:" + std::to_string(player_id) + "\n";
_vfs_repository->create_node(machine_id, &etc_id, "group", FILE_NODE,
group_content, 0, 0, 0644);
/* Create /bin and get it's ID */
long long bin_id = _vfs_repository->create_node(machine_id, &root_id, "bin", DIR_NODE,

View File

@ -47,3 +47,11 @@ std::string MachineRepository::get_hostname(long long machine_id) {
>> hostname;
return hostname;
}
long long MachineRepository::get_owner_id(long long machine_id) {
long long owner_id = -1;
_db << "SELECT owner_id FROM machines WHERE id = ?;"
<< machine_id
>> owner_id;
return owner_id;
}

View File

@ -21,6 +21,7 @@ public:
std::vector<MachineData> get_all_npcs(void);
std::vector<MachineData> get_all(void);
std::string get_hostname(long long machine_id);
long long get_owner_id(long long machine_id);
private:
sqlite::database& _db;

View File

@ -1,3 +1,4 @@
#include <exception>
#include <sol/call.hpp>
#include <sol/types.hpp>
#include <sstream>
@ -72,7 +73,9 @@ std::string write_file(Session& context, const std::string& path,
it->second->content = content;
} else {
/* File does not exist, create it. */
vfs_node* new_file = new_node(filename, FILE_NODE, parent_dir);
uint32_t current_uid = context.get_home_machine()->owner_id;
vfs_node* new_file = new_node(filename, FILE_NODE, parent_dir, current_uid,
current_uid, 0644);
new_file->content = content;
parent_dir->children[filename] = new_file;
}
@ -117,12 +120,18 @@ std::string create_executable(Session& context, const std::string& path,
delete it->second;
parent_dir->children.erase(it);
}
vfs_node* new_exec = new_node(filename, EXEC_NODE, parent_dir);
uint32_t current_uid = context.get_home_machine()->owner_id;
vfs_node* new_exec = new_node(filename, EXEC_NODE, parent_dir, current_uid,
current_uid, 0755);
new_exec->content = util::xor_string(content);
parent_dir->children[filename] = new_exec;
return "";
}
std::string read_file(Session& context, const std::string& path) {
return context.read_file(path);
}
std::string cd(Session& context, const std::string& path) {
vfs_node* current_dir = context.get_current_dir();
@ -213,6 +222,104 @@ std::string close_terminal(Session& context) {
return "__CLOSE_CONNECTION__";
}
std::string get_username(Session& context) {
return context.get_username();
}
long long get_uid_from_name(Session& context, const std::string& username) {
vfs_node* passwd_node = find_node_by_path(context.get_home_machine()->vfs_root, "/etc/passwd");
if(!passwd_node || passwd_node->type != FILE_NODE) {
return -1;
}
std::stringstream ss(passwd_node->content);
std::string line;
while(std::getline(ss, line)) {
std::vector<std::string> parts;
std::stringstream line_ss(line);
std::string part;
while(std::getline(line_ss, part, ':')) {
parts.push_back(part);
}
if(parts.size() >= 3 && parts[0] == username) {
try {
return std::stoll(parts[2]);
} catch(const std::exception& e) {
return -1;
}
}
}
return -1;
}
std::string chown(Session& context, const std::string& owner, const std::string& path) {
vfs_node* node = find_node_by_path(context.get_session_machine()->vfs_root, path);
if(!node) {
return "chown: cannot access '" + path + "' No such file or directory";
}
long long new_uid = get_uid_from_name(context, owner);
if(new_uid == -1) {
return "chown: invalid user: '" + owner + "'";
}
uint32_t current_uid = context.get_home_machine()->owner_id;
if(node->owner_id != current_uid && current_uid != 0) {
return "chown: changing ownership of '" + path + "': Operation not permitted";
}
node->owner_id = new_uid;
return "";
}
long long get_gid_from_name(Session& context, const std::string& groupname) {
vfs_node* group_node = find_node_by_path(context.get_home_machine()->vfs_root, "/etc/group");
if(!group_node || group_node->type != FILE_NODE) {
return -1;
}
std::stringstream ss(group_node->content);
std::string line;
while(std::getline(ss, line)) {
std::vector<std::string> parts;
std::stringstream line_ss(line);
std::string part;
while(std::getline(line_ss, part, ':')) {
parts.push_back(part);
}
if(parts.size() >= 3 && parts[0] == groupname) {
try {
return std::stoll(parts[2]);
} catch(const std::exception& e) {
return -1;
}
}
}
return -1;
}
std::string chgrp(Session& context, const std::string& group, const std::string& path) {
vfs_node* node = find_node_by_path(context.get_session_machine()->vfs_root, path);
if(!node) {
return "chgrp: cannot access '" + path + "': No such file ordirectory";
}
long long new_gid = get_gid_from_name(context, group);
if(new_gid == -1) {
return "chgrp: invalid group: '" + group + "'";
}
uint32_t current_uid = context.get_home_machine()->owner_id;
if(node->owner_id != current_uid && current_uid != 0) {
return "chgrp: changing group of '" + path + "': Operation not permitted";
}
node->group_id = new_gid;
return "";
}
struct ScpPath {
std::string host;
std::string path;

View File

@ -15,10 +15,15 @@ std::string write_file(Session& context, const std::string& path,
const std::string& content);
std::string create_executable(Session& context, const std::string& path,
const std::string& content);
std::string read_file(Session& context, const std::string& path);
std::string ls(Session& context);
std::string cd(Session& context, const std::string& path);
std::string scp(Session& context, const std::string& source,
const std::string& destination);
std::string chown(Session& context, const std::string& owner,
const std::string& path);
std::string chgrp(Session& context, const std::string& group,
const std::string& path);
/* NETWORK ACTIONS. */
std::string ssh(Session& context, const std::string& ip);
@ -26,6 +31,7 @@ std::string nmap(Session& context, const std::string& ip);
std::string disconnect(Session& context);
/* SYSTEM ACTIONS. */
std::string get_username(Session& context);
std::string close_terminal(Session& context);
} /* namespace api */

View File

@ -44,13 +44,17 @@ LuaProcessor::LuaProcessor(Session& context) {
bettola_api.set_function("ls", &api::ls);
bettola_api.set_function("write_file", &api::write_file);
bettola_api.set_function("create_executable", &api::create_executable);
bettola_api.set_function("read_file", &api::read_file);
bettola_api.set_function("get_current_dir", &api::get_current_dir);
bettola_api.set_function("cd", &api::cd);
bettola_api.set_function("scp", &api::scp);
bettola_api.set_function("chown", &api::chown);
bettola_api.set_function("chgrp", &api::chgrp);
bettola_api.set_function("close_terminal", &api::close_terminal);
bettola_api.set_function("ssh", &api::ssh);
bettola_api.set_function("nmap", &api::nmap);
bettola_api.set_function("disconnect", &api::disconnect);
bettola_api.set_function("get_username", &api::get_username);
}
LuaProcessor::~LuaProcessor(void) {}

View File

@ -9,9 +9,10 @@
class Machine {
public:
Machine(uint32_t id, std::string hostname) :
Machine(uint32_t id, std::string hostname, uint32_t owner_id = 0) :
id(id),
hostname(std::move(hostname)),
owner_id(owner_id),
vfs_root(nullptr),
is_vfs_a_copy(false) {}
@ -19,6 +20,7 @@ public:
uint32_t id;
std::string hostname;
uint32_t owner_id;
vfs_node* vfs_root;
std::map<int, std::string> services;
bool is_vfs_a_copy; /* Flag for CoW mechanism. */

View File

@ -109,8 +109,9 @@ Machine* MachineManager::load_machine(long long machine_id) {
printf("DEBUG: load_machine called for machine_id: %lld\n", machine_id);
std::string hostname = _db_manager->machines().get_hostname(machine_id);
long long owner_id = _db_manager->machines().get_owner_id(machine_id);
Machine* machine = new Machine(machine_id, hostname);
Machine* machine = new Machine(machine_id, hostname, owner_id);
/* Load all VFS nodes for this machine from the database. */
std::map<long long, vfs_node*> node_map;

View File

@ -1,5 +1,6 @@
#include <sol/types.hpp>
#include <sstream>
#include <vector>
#include "session.h"
#include "db/database_manager.h"
@ -149,3 +150,34 @@ std::string Session::read_file(const std::string& path) {
}
return "Error: file not found.";
}
std::string Session::get_username(void) {
if(!_home_machine || !_home_machine->vfs_root) {
return "unknown";
}
vfs_node* passwd_node = find_node_by_path(_home_machine->vfs_root, "/etc/passwd");
if(!passwd_node || passwd_node->type != FILE_NODE) {
return "unknown";
}
std::stringstream ss(passwd_node->content);
std::string line;
uint32_t player_id = _home_machine->owner_id;
while(std::getline(ss, line)) {
std::vector<std::string> parts;
std::stringstream line_ss(line);
std::string part;
while(std::getline(line_ss, part, ':')) {
parts.push_back(part);
}
if(parts.size() >= 3) {
if(std::to_string(player_id) == parts[2]) {
return parts[0]; /* Username. */
}
}
}
return "unknown";
}

View File

@ -19,6 +19,8 @@ public:
std::string write_file(const std::string& path, const std::string& content);
std::string read_file(const std::string& path);
std::string get_username(void);
/* Public interface for API functions. */
vfs_node* get_current_dir(void);
Machine* get_home_machine(void);