urbackup_backend/urbackupclient/lin_tokens.cpp
2021-07-05 19:49:25 +02:00

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