mirror of
https://github.com/uroni/urbackup_backend.git
synced 2025-10-26 11:36:50 +00:00
712 lines
14 KiB
C++
712 lines
14 KiB
C++
#include "tokens.h"
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <map>
|
|
#include <grp.h>
|
|
#include <pwd.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include "../stringtools.h"
|
|
#include "../Interface/Server.h"
|
|
#include "../common/data.h"
|
|
#include <memory.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <limits.h>
|
|
|
|
namespace tokens
|
|
{
|
|
|
|
struct Token
|
|
{
|
|
int64 id;
|
|
bool is_user;
|
|
};
|
|
|
|
struct TokenCacheInt
|
|
{
|
|
std::map<uid_t, int64> uid_map;
|
|
std::map<gid_t, int64> gid_map;
|
|
};
|
|
|
|
TokenCache::TokenCache()
|
|
{
|
|
}
|
|
|
|
TokenCache::~TokenCache()
|
|
{
|
|
}
|
|
|
|
void TokenCache::reset(TokenCacheInt* cache)
|
|
{
|
|
token_cache.reset(cache);
|
|
}
|
|
|
|
TokenCacheInt* TokenCache::get()
|
|
{
|
|
return token_cache.get();
|
|
}
|
|
|
|
void TokenCache::operator=(const TokenCache& other)
|
|
{
|
|
assert(false);
|
|
}
|
|
|
|
TokenCache::TokenCache(const TokenCache& other)
|
|
{
|
|
assert(false);
|
|
}
|
|
|
|
std::string get_hostname()
|
|
{
|
|
char hostname_c[255];
|
|
hostname_c[0]=0;
|
|
gethostname(hostname_c, 255);
|
|
|
|
std::string hostname = hostname_c;
|
|
if(!hostname.empty())
|
|
hostname+="\\";
|
|
|
|
return hostname;
|
|
}
|
|
|
|
std::vector<std::map<std::string, std::string> > read_kv(const std::string& cmd)
|
|
{
|
|
std::string data;
|
|
int rc = os_popen(cmd, data);
|
|
std::vector<std::map<std::string, std::string> > ret;
|
|
if(rc!=0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
std::map<std::string, std::string> cur;
|
|
std::istringstream iss(data);
|
|
for(std::string line; std::getline(iss, line);)
|
|
{
|
|
if(trim(line).empty())
|
|
{
|
|
if(!cur.empty())
|
|
{
|
|
ret.push_back(cur);
|
|
}
|
|
cur.clear();
|
|
}
|
|
|
|
std::string key = strlower(trim(getuntil(":", line)));
|
|
std::string value = trim(getafter(":", line));
|
|
|
|
cur[key]=value;
|
|
}
|
|
|
|
if(!cur.empty())
|
|
{
|
|
ret.push_back(cur);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::vector<std::string> read_single_k(const std::string& col, const std::string& cmd)
|
|
{
|
|
std::vector<std::map<std::string, std::string> > res = read_kv(cmd);
|
|
std::vector<std::string> ret;
|
|
for(size_t i=0;i<res.size();++i)
|
|
{
|
|
std::string single = trim(res[i][col]);
|
|
if(!single.empty())
|
|
{
|
|
ret.push_back(single);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::vector<std::string> get_users()
|
|
{
|
|
#ifndef __APPLE__
|
|
std::ifstream passwd("/etc/passwd");
|
|
|
|
if(!passwd.is_open())
|
|
{
|
|
return std::vector<std::string>();
|
|
}
|
|
|
|
std::vector<std::string> ret;
|
|
for(std::string line; std::getline(passwd, line);)
|
|
{
|
|
if(line.empty() || line[0]=='#')
|
|
{
|
|
continue;
|
|
}
|
|
std::string user = trim(getuntil(":", line));
|
|
if(!user.empty())
|
|
{
|
|
ret.push_back(user);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
#else
|
|
return read_single_k("name", "dscacheutil -q user");
|
|
#endif
|
|
}
|
|
|
|
int read_val(std::string val_name)
|
|
{
|
|
std::string data=getFile("/etc/login.defs");
|
|
|
|
size_t off = data.find(val_name);
|
|
|
|
if(off!=std::string::npos)
|
|
{
|
|
size_t noff = data.find("\n", off);
|
|
|
|
if(noff!=std::string::npos)
|
|
{
|
|
return atoi(trim(data.substr(off+val_name.size(), noff - off - val_name.size())).c_str());
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
std::vector<std::string> get_local_users()
|
|
{
|
|
std::vector<std::string> users = get_users();
|
|
std::vector<std::string> ret;
|
|
|
|
for(size_t i=0;i<users.size();++i)
|
|
{
|
|
struct passwd* pw = getpwnam(users[i].c_str());
|
|
if(pw!=nullptr)
|
|
{
|
|
#ifndef __APPLE__
|
|
static int uid_min = read_val("UID_MIN");
|
|
static int uid_max = read_val("UID_MAX");
|
|
#else
|
|
static int uid_min = 500;
|
|
static int uid_max = INT_MAX;
|
|
#endif
|
|
|
|
if( (uid_min==-1 || uid_max==-1 || (
|
|
pw->pw_uid >= uid_min &&
|
|
pw->pw_uid <= uid_max )) &&
|
|
strlen(pw->pw_dir)>0 &&
|
|
os_directory_exists(pw->pw_dir))
|
|
{
|
|
ret.push_back(users[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::vector<std::string> get_groups()
|
|
{
|
|
#ifndef __APPLE__
|
|
std::ifstream group("/etc/group");
|
|
|
|
if(!group.is_open())
|
|
{
|
|
return std::vector<std::string>();
|
|
}
|
|
|
|
std::vector<std::string> ret;
|
|
for(std::string line; std::getline(group, line);)
|
|
{
|
|
if(line.empty() || line[0]=='#')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
std::string cgroup = trim(getuntil(":", line));
|
|
if(!cgroup.empty())
|
|
{
|
|
ret.push_back(cgroup);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
#else
|
|
return read_single_k("name", "dscacheutil -q group");
|
|
#endif
|
|
}
|
|
|
|
std::vector<std::string> get_user_groups(const std::string& username)
|
|
{
|
|
#ifdef __APPLE__
|
|
#define gid_t int
|
|
#endif
|
|
std::string utf8_username = (username).c_str();
|
|
struct passwd* pw = getpwnam(utf8_username.c_str());
|
|
if(pw==nullptr)
|
|
{
|
|
Server->Log("Error getting passwd structure for user with name \""+ username + "\"", LL_ERROR);
|
|
return std::vector<std::string>();
|
|
}
|
|
|
|
std::vector<gid_t> group_ids;
|
|
|
|
int ngroups = 0;
|
|
|
|
if(getgrouplist(utf8_username.c_str(), pw->pw_gid, group_ids.data(), &ngroups)==-1)
|
|
{
|
|
group_ids.resize(ngroups);
|
|
if(getgrouplist(utf8_username.c_str(), pw->pw_gid, group_ids.data(), &ngroups)==-1)
|
|
{
|
|
Server->Log("Error getting group ids for user with name \""+ username + "\"", LL_ERROR);
|
|
return std::vector<std::string>();
|
|
}
|
|
|
|
std::vector<std::string> ret;
|
|
for(size_t i=0;i<group_ids.size();++i)
|
|
{
|
|
struct group* gr = getgrgid(group_ids[i]);
|
|
if(gr==nullptr)
|
|
{
|
|
Server->Log("Error getting group name of group id "+convert(group_ids[i])+" while getting groups of user with name \""+username+"\"", LL_ERROR);
|
|
}
|
|
else
|
|
{
|
|
ret.push_back(gr->gr_name);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
else
|
|
{
|
|
return std::vector<std::string>();
|
|
}
|
|
|
|
#ifdef gid_t
|
|
#undef gid_t
|
|
#endif
|
|
}
|
|
|
|
|
|
bool write_token( std::string hostname, bool is_user, std::string accountname, const std::string &token_fn, ClientDAO &dao, const std::string& ext_token)
|
|
{
|
|
int token_fd = creat((token_fn).c_str(), is_user?S_IRWXU:S_IRWXG);
|
|
|
|
if(token_fd==-1)
|
|
{
|
|
Server->Log("Error creating token file "+token_fn+" errno="+convert(errno), LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
bool is_system_user = false;
|
|
|
|
if(is_user)
|
|
{
|
|
struct passwd* pw = getpwnam((accountname).c_str());
|
|
if(pw!=nullptr)
|
|
{
|
|
#ifndef __APPLE__
|
|
static int uid_min = read_val("UID_MIN");
|
|
static int uid_max = read_val("UID_MAX");
|
|
#else
|
|
static int uid_min = 500;
|
|
static int uid_max = INT_MAX;
|
|
#endif
|
|
|
|
if( uid_min!=-1 && uid_max!=-1 && (
|
|
pw->pw_uid < uid_min ||
|
|
pw->pw_uid > uid_max ) )
|
|
{
|
|
is_system_user = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string token;
|
|
if(ext_token.empty())
|
|
{
|
|
token = Server->secureRandomString(20);
|
|
}
|
|
else
|
|
{
|
|
token = ext_token;
|
|
}
|
|
|
|
dao.updateFileAccessToken(accountname_normalize(accountname), token,
|
|
is_user ? (is_system_user ? ClientDAO::c_is_system_user : ClientDAO::c_is_user) : ClientDAO::c_is_group );
|
|
|
|
size_t written = 0;
|
|
while(written<token.size())
|
|
{
|
|
ssize_t w = write(token_fd, token.data() + written, token.size()-written);
|
|
if(w<=0 && errno!=EINTR)
|
|
{
|
|
Server->Log("Error writing to token file. Errno="+convert(errno), LL_ERROR);
|
|
close(token_fd);
|
|
return false;
|
|
}
|
|
else if(w>0)
|
|
{
|
|
written+=w;
|
|
}
|
|
}
|
|
|
|
if(is_user)
|
|
{
|
|
struct passwd* pw = getpwnam(accountname.c_str());
|
|
if(pw==nullptr)
|
|
{
|
|
Server->Log("Error getting passwd structure for user with name \""+ accountname + "\"", LL_ERROR);
|
|
close(token_fd);
|
|
return false;
|
|
}
|
|
|
|
if(fchown(token_fd, pw->pw_uid, pw->pw_gid)!=0)
|
|
{
|
|
Server->Log("Error changing user ownership of token file \""+ token_fn + "\"", LL_ERROR);
|
|
close(token_fd);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
struct group* gr = getgrnam(accountname.c_str());
|
|
if(gr==nullptr)
|
|
{
|
|
Server->Log("Error getting group structure for group with name \""+ accountname + "\"", LL_ERROR);
|
|
close(token_fd);
|
|
return false;
|
|
}
|
|
|
|
if(fchown(token_fd, 0, gr->gr_gid)!=0)
|
|
{
|
|
Server->Log("Error changing group ownership of token file \""+ token_fn + "\"", LL_ERROR);
|
|
close(token_fd);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
close(token_fd);
|
|
return true;
|
|
}
|
|
|
|
void read_all_tokens(ClientDAO* dao, TokenCache& token_cache)
|
|
{
|
|
TokenCacheInt* cache = new TokenCacheInt;
|
|
token_cache.reset(cache);
|
|
|
|
std::vector<std::string> users = get_users();
|
|
|
|
for(size_t i=0;i<users.size();++i)
|
|
{
|
|
struct passwd* pw = getpwnam(users[i].c_str());
|
|
|
|
if(pw==nullptr)
|
|
{
|
|
Server->Log("Error getting passwd structure for user with name \""+ users[i] + "\"", LL_DEBUG);
|
|
continue;
|
|
}
|
|
|
|
ClientDAO::CondInt64 token_id = dao->getFileAccessTokenId2Alts(accountname_normalize(users[i]), ClientDAO::c_is_user, ClientDAO::c_is_system_user);
|
|
|
|
if(!token_id.exists)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
cache->uid_map[pw->pw_uid] = token_id.value;
|
|
}
|
|
|
|
std::vector<std::string> groups = get_groups();
|
|
|
|
for(size_t i=0;i<groups.size();++i)
|
|
{
|
|
struct group* gr = getgrnam(groups[i].c_str());
|
|
|
|
if(gr==nullptr)
|
|
{
|
|
Server->Log("Error getting group structure for group with name \""+ groups[i] + "\"", LL_DEBUG);
|
|
continue;
|
|
}
|
|
|
|
ClientDAO::CondInt64 token_id = dao->getFileAccessTokenId(accountname_normalize(groups[i]), ClientDAO::c_is_group);
|
|
|
|
if(!token_id.exists)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
cache->gid_map[gr->gr_gid] = token_id.value;
|
|
}
|
|
}
|
|
|
|
std::string translate_tokens(uid_t uid, gid_t gid, mode_t mode, ClientDAO* dao, ETokenRight right, TokenCache& cache);
|
|
|
|
std::string get_file_tokens( const std::string& fn, ClientDAO* dao, ETokenRight right, TokenCache& token_cache )
|
|
{
|
|
struct stat stat_data;
|
|
|
|
int rc = stat(fn.c_str(), &stat_data);
|
|
|
|
if(rc!=0 && errno==ENOENT)
|
|
{
|
|
//TODO: Return permissions of symlink and rework permission handling to separate symlink from normal file permissions
|
|
rc = lstat(fn.c_str(), &stat_data);
|
|
|
|
if(rc!=0)
|
|
{
|
|
//For now permit only root
|
|
return translate_tokens(0, 0,
|
|
stat_data.st_mode & ~(S_IROTH|S_IXOTH|S_IWOTH|S_IRGRP|S_IXGRP|S_IWGRP),
|
|
dao, right, token_cache);
|
|
}
|
|
}
|
|
|
|
if(rc!=0)
|
|
{
|
|
Server->Log("Error stating file \"" + fn + "\" to get file tokens. Errno: "+convert(errno), LL_ERROR);
|
|
return std::string();
|
|
}
|
|
|
|
return translate_tokens(stat_data.st_uid, stat_data.st_gid, stat_data.st_mode, dao, right, token_cache);
|
|
}
|
|
|
|
int64 read_token_lazy_cache(TokenCache& token_cache, ClientDAO* dao, bool is_user, int64 uid)
|
|
{
|
|
if(is_user)
|
|
{
|
|
std::map<uid_t, int64>::iterator it = token_cache.get()->uid_map.find(uid);
|
|
if(it!=token_cache.get()->uid_map.end())
|
|
return it->second;
|
|
}
|
|
else
|
|
{
|
|
std::map<uid_t, int64>::iterator it = token_cache.get()->gid_map.find(uid);
|
|
if(it!=token_cache.get()->gid_map.end())
|
|
return it->second;
|
|
}
|
|
|
|
std::string name;
|
|
if(is_user)
|
|
{
|
|
struct passwd* pw = getpwuid(uid);
|
|
if(pw!=nullptr)
|
|
{
|
|
name = pw->pw_name;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
struct group* grp = getgrgid(uid);
|
|
if(grp!=nullptr)
|
|
{
|
|
name = grp->gr_name;
|
|
}
|
|
}
|
|
|
|
if(name.empty())
|
|
{
|
|
if(is_user)
|
|
token_cache.get()->uid_map[uid]=0;
|
|
else
|
|
token_cache.get()->gid_map[uid]=0;
|
|
return 0;
|
|
}
|
|
|
|
|
|
ClientDAO::CondInt64 token_id;
|
|
|
|
if (is_user)
|
|
{
|
|
token_id = dao->getFileAccessTokenId2Alts(name,
|
|
ClientDAO::c_is_user, ClientDAO::c_is_system_user);
|
|
}
|
|
else
|
|
{
|
|
token_id = dao->getFileAccessTokenId(name, ClientDAO::c_is_group);
|
|
}
|
|
|
|
if(token_id.exists)
|
|
{
|
|
if(is_user)
|
|
{
|
|
token_cache.get()->uid_map[uid]=token_id.value;
|
|
}
|
|
else
|
|
{
|
|
token_cache.get()->gid_map[uid]=token_id.value;
|
|
}
|
|
return token_id.value;
|
|
}
|
|
|
|
std::string user_fn = (is_user ? "user_" : "group_") + bytesToHex(name);
|
|
std::string token_fn = tokens_path + os_file_sep() + user_fn;
|
|
if (!write_token(get_hostname(), is_user, name, token_fn, *dao))
|
|
{
|
|
if(is_user)
|
|
token_cache.get()->uid_map[uid]=0;
|
|
else
|
|
token_cache.get()->gid_map[uid]=0;
|
|
return 0;
|
|
}
|
|
|
|
if (is_user)
|
|
{
|
|
token_id = dao->getFileAccessTokenId2Alts(name,
|
|
ClientDAO::c_is_user, ClientDAO::c_is_system_user);
|
|
}
|
|
else
|
|
{
|
|
token_id = dao->getFileAccessTokenId(name, ClientDAO::c_is_group);
|
|
}
|
|
|
|
|
|
if(token_id.exists)
|
|
{
|
|
if(is_user)
|
|
{
|
|
token_cache.get()->uid_map[uid]=token_id.value;
|
|
}
|
|
else
|
|
{
|
|
token_cache.get()->gid_map[uid]=token_id.value;
|
|
}
|
|
return token_id.value;
|
|
}
|
|
else
|
|
{
|
|
if(is_user)
|
|
token_cache.get()->uid_map[uid]=0;
|
|
else
|
|
token_cache.get()->gid_map[uid]=0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
std::string translate_tokens(uid_t uid, gid_t gid, mode_t mode, ClientDAO* dao, ETokenRight right, TokenCache& cache)
|
|
{
|
|
if(cache.get()==nullptr)
|
|
{
|
|
read_all_tokens(dao, cache);
|
|
}
|
|
|
|
CWData token_info;
|
|
|
|
mode_t iall;
|
|
mode_t iusr;
|
|
mode_t igrp;
|
|
|
|
switch(right)
|
|
{
|
|
case ETokenRight_Read:
|
|
if(S_ISDIR(mode))
|
|
{
|
|
iall = S_IROTH | S_IXOTH;
|
|
iusr = S_IRUSR | S_IXUSR;
|
|
igrp = S_IRGRP | S_IXGRP;
|
|
}
|
|
else
|
|
{
|
|
iall = S_IROTH;
|
|
iusr = S_IRUSR;
|
|
igrp = S_IRGRP;
|
|
}
|
|
break;
|
|
case ETokenRight_Write:
|
|
iall = S_IWOTH;
|
|
iusr = S_IWUSR;
|
|
igrp = S_IWGRP;
|
|
break;
|
|
case ETokenRight_DeleteFromDir:
|
|
iall = S_IROTH | S_IWOTH;
|
|
iusr = S_IRUSR | S_IWUSR;
|
|
igrp = S_IRGRP | S_IWGRP;
|
|
break;
|
|
};
|
|
|
|
|
|
if(right == ETokenRight_Delete)
|
|
{
|
|
//Allow all
|
|
token_info.addChar(ID_GRANT_ACCESS);
|
|
token_info.addVarInt(0);
|
|
}
|
|
else if( (mode & iall) == iall)
|
|
{
|
|
//Allow all
|
|
token_info.addChar(ID_GRANT_ACCESS);
|
|
token_info.addVarInt(0);
|
|
}
|
|
else
|
|
{
|
|
//Allow root
|
|
{
|
|
int64 tid = read_token_lazy_cache(cache, dao, true, 0);
|
|
if(tid!=0)
|
|
{
|
|
token_info.addChar(ID_GRANT_ACCESS);
|
|
token_info.addVarInt(tid);
|
|
}
|
|
}
|
|
|
|
if( (mode & iusr) == iusr)
|
|
{
|
|
int64 tid = read_token_lazy_cache(cache, dao, true, uid);
|
|
if(tid==0)
|
|
{
|
|
Server->Log("Error getting internal id for user with id "+convert(uid), LL_ERROR);
|
|
}
|
|
else
|
|
{
|
|
token_info.addChar(ID_GRANT_ACCESS);
|
|
token_info.addVarInt(tid);
|
|
}
|
|
}
|
|
|
|
if( (mode & igrp) == igrp)
|
|
{
|
|
int64 tid = read_token_lazy_cache(cache, dao, false, uid);
|
|
if(tid==0)
|
|
{
|
|
Server->Log("Error getting internal id for group with id "+convert(gid), LL_ERROR);
|
|
}
|
|
else
|
|
{
|
|
token_info.addChar(ID_GRANT_ACCESS);
|
|
token_info.addVarInt(tid);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string ret;
|
|
ret.resize(token_info.getDataSize());
|
|
memcpy(&ret[0], token_info.getDataPtr(), token_info.getDataSize());
|
|
return ret;
|
|
}
|
|
|
|
std::string permissions_allow_all()
|
|
{
|
|
CWData token_info;
|
|
//allow to all
|
|
token_info.addChar(ID_GRANT_ACCESS);
|
|
token_info.addVarInt(0);
|
|
|
|
return std::string(token_info.getDataPtr(), token_info.getDataSize());
|
|
}
|
|
|
|
std::string get_domain_account(const std::string& accountname)
|
|
{
|
|
return accountname;
|
|
}
|
|
|
|
std::string accountname_normalize(const std::string& accountname)
|
|
{
|
|
//TODO: Make this conditional on LDAP being used and LDAP being case-insensitive
|
|
return accountname;
|
|
}
|
|
|
|
} //namespace tokens
|