diff --git a/assets/scripts/bin/chgrp.lua b/assets/scripts/bin/chgrp.lua new file mode 100644 index 0000000..709512d --- /dev/null +++ b/assets/scripts/bin/chgrp.lua @@ -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) diff --git a/assets/scripts/bin/chown.lua b/assets/scripts/bin/chown.lua new file mode 100644 index 0000000..78887f4 --- /dev/null +++ b/assets/scripts/bin/chown.lua @@ -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) diff --git a/assets/scripts/bin/ls.lua b/assets/scripts/bin/ls.lua index 45aa967..45fbf86 100644 --- a/assets/scripts/bin/ls.lua +++ b/assets/scripts/bin/ls.lua @@ -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") diff --git a/assets/scripts/bin/whoami.lua b/assets/scripts/bin/whoami.lua new file mode 100644 index 0000000..f1eab6c --- /dev/null +++ b/assets/scripts/bin/whoami.lua @@ -0,0 +1,2 @@ +-- /bin/whoami - Print the effective username of the current user. +return bettola.get_username(context) diff --git a/common/src/db/database_manager.cpp b/common/src/db/database_manager.cpp index 343307f..b61d45f 100644 --- a/common/src/db/database_manager.cpp +++ b/common/src/db/database_manager.cpp @@ -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, diff --git a/common/src/db/machine_repository.cpp b/common/src/db/machine_repository.cpp index 01b0152..fcbe43a 100644 --- a/common/src/db/machine_repository.cpp +++ b/common/src/db/machine_repository.cpp @@ -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; +} diff --git a/common/src/db/machine_repository.h b/common/src/db/machine_repository.h index c0af20a..1d10817 100644 --- a/common/src/db/machine_repository.h +++ b/common/src/db/machine_repository.h @@ -21,6 +21,7 @@ public: std::vector get_all_npcs(void); std::vector get_all(void); std::string get_hostname(long long machine_id); + long long get_owner_id(long long machine_id); private: sqlite::database& _db; diff --git a/common/src/lua_api.cpp b/common/src/lua_api.cpp index 84e0c32..8d9a739 100644 --- a/common/src/lua_api.cpp +++ b/common/src/lua_api.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -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 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 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; diff --git a/common/src/lua_api.h b/common/src/lua_api.h index fd27bd3..0dd5f2a 100644 --- a/common/src/lua_api.h +++ b/common/src/lua_api.h @@ -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 */ diff --git a/common/src/lua_processor.cpp b/common/src/lua_processor.cpp index d74c3ee..d7c3185 100644 --- a/common/src/lua_processor.cpp +++ b/common/src/lua_processor.cpp @@ -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) {} diff --git a/common/src/machine.h b/common/src/machine.h index ebad545..90e2005 100644 --- a/common/src/machine.h +++ b/common/src/machine.h @@ -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 services; bool is_vfs_a_copy; /* Flag for CoW mechanism. */ diff --git a/common/src/machine_manager.cpp b/common/src/machine_manager.cpp index 2ba4ccb..7efeba3 100644 --- a/common/src/machine_manager.cpp +++ b/common/src/machine_manager.cpp @@ -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 node_map; diff --git a/common/src/session.cpp b/common/src/session.cpp index a47a01f..451a42d 100644 --- a/common/src/session.cpp +++ b/common/src/session.cpp @@ -1,5 +1,6 @@ #include #include +#include #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 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"; +} diff --git a/common/src/session.h b/common/src/session.h index ffa5cb7..5ca7b36 100644 --- a/common/src/session.h +++ b/common/src/session.h @@ -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);