Newer
Older
/* Copyright 2021 Dominik George <dominik.george@teckids.org>
* Copyright 2021 mirabilos <thorsten.glaser@teckids.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

Nik | Klampfradler
committed
use crate::unix::{Passwd, getpwnam_safe, getpwuid_safe};
use lazy_static::lazy_static;

Nik | Klampfradler
committed
use std::sync::{Mutex, MutexGuard};
use libc::{geteuid, seteuid, uid_t};
use oauth2::basic::BasicTokenResponse;
use std::io;
use std::path::PathBuf;
use serde::Serialize;
use serde::de::DeserializeOwned;
use serde_json;
const USER_TOKEN_FILENAME: &str = "user_token.json";
pub struct UserInfo<'a> {

Nik | Klampfradler
committed
uid: Option<uid_t>,
username: Option<String>,
passwd: Option<Passwd<'a>>,

Nik | Klampfradler
committed
access_token: Option<BasicTokenResponse>
}
pub struct Cache<'a> {
pub context_user: UserInfo<'a>
impl Cache<'_> {
pub fn new<'a>() -> Cache<'a> {

Nik | Klampfradler
committed
let euid = unsafe {
geteuid()

Nik | Klampfradler
committed
context_user: UserInfo {
uid: None,
username: None,
passwd: None,
access_token: None
}

Nik | Klampfradler
committed
}
impl <'a>UserInfo<'a> {

Nik | Klampfradler
committed
pub fn set_current_user(&mut self) {
self.set_uid(get_original_euid());

Nik | Klampfradler
committed
}
pub fn is_initialized(&self) -> bool {
self.uid.is_some() || self.username.is_some()
}
fn try_resolve(&mut self) -> Result<(), io::Error> {
// If we already have a full passwd struct, do nothing

Nik | Klampfradler
committed
if self.passwd.is_some() {
debug!("passwd entry for context user already resolved");
return Ok(());

Nik | Klampfradler
committed
}
// If we cannot call getpwnam safely, return error (see `is_get_pwnam_safe`)

Nik | Klampfradler
committed
if !get_is_getpwnam_safe() {

Nik | Klampfradler
committed
let msg = "Context user cannot be resolved safely right now";
warn!("{}", msg);
return Err(io::Error::new(io::ErrorKind::WouldBlock, msg));
}
// Try one of the partial information to resolve
let res = match self.uid {
Some(uid) => getpwuid_safe(uid),
None => match &self.username {
Some(username) => getpwnam_safe(username.to_string()),
None => {
let msg = "No partial information set to use for getpwnam / getpwuid";
warn!("{}", msg);
Err(io::Error::new(io::ErrorKind::InvalidInput, msg))
}
}

Nik | Klampfradler
committed
match res {
Ok(passwd) => {
debug!("Successfully resolved context user's passwd entry");
self.passwd = Some(passwd);
Ok(())

Nik | Klampfradler
committed
},
Err(e) => Err(e)
}
}
pub fn get_uid(&mut self) -> Result<uid_t, io::Error> {
self.try_resolve();
match &self.passwd {
Some(passwd) => Ok(passwd.pw_uid),
None => match self.uid {

Nik | Klampfradler
committed
Some(uid) => Ok(uid),
None => Err(io::Error::new(io::ErrorKind::InvalidInput, "foo"))

Nik | Klampfradler
committed
}
}
}
pub fn set_uid(&mut self, uid: uid_t) {
self.uid = Some(uid);
self.username = None;
self.passwd = None;
self.try_resolve();
}
pub fn get_username(&mut self) -> Result<String, io::Error> {
self.try_resolve();
match &self.passwd {
Some(passwd) => Ok(passwd.pw_name.to_string()),
None => match &self.username {
Some(username) => Ok(username.to_string()),
None => Err(io::Error::new(io::ErrorKind::InvalidInput, "foo"))

Nik | Klampfradler
committed
}
}
}
pub fn set_username(&mut self, username: String) {

Nik | Klampfradler
committed
self.username = Some(username);
self.uid = None;
self.passwd = None;
self.try_resolve();
}
pub fn get_home_directory(&mut self) -> Result<&str, io::Error> {
self.try_resolve();
match &self.passwd {
Some(passwd) => Ok(passwd.pw_dir),
None => Err(io::Error::new(io::ErrorKind::InvalidInput, "foo"))

Nik | Klampfradler
committed
}
}
fn drop_privileges(&mut self) -> Result<uid_t, io::Error> {

Nik | Klampfradler
committed
let current_euid = unsafe {
geteuid()
};
let target_euid = match self.get_uid() {
Ok(uid) => uid,

Nik | Klampfradler
committed
debug!("Could not drop privileges because target UID is not resolved");
if target_euid == current_euid {

Nik | Klampfradler
committed
debug!("No need to drop privileges, already running as {}", current_euid);

Nik | Klampfradler
committed
let res = unsafe {
seteuid(target_euid)

Nik | Klampfradler
committed
debug!("Successfully dropped privileges to {}", target_euid);
return Ok(target_euid);
} else {
let e = io::Error::last_os_error();

Nik | Klampfradler
committed
error!("Could not drop privileges to {}", target_euid);
}
fn restore_privileges(&self) {

Nik | Klampfradler
committed
let current_euid = unsafe {
geteuid()
if current_euid != get_original_euid() {
debug!("Restoring privileges");

Nik | Klampfradler
committed
let res = unsafe {
seteuid(get_original_euid())
panic!("Could not restore privileges to {}", get_original_euid());
} else {
debug!("No need to restore privileges, already running as original user");

Nik | Klampfradler
committed
fn get_user_xdg_base_directories(&mut self) -> Result<BaseDirectories, io::Error> {
// Save original $HOME for later restore
let saved_home = env::var_os("HOME");

Nik | Klampfradler
committed
// Determine user ID to find out whether we should override $HOME
let uid = self.get_uid()?;
if uid != get_original_euid() {

Nik | Klampfradler
committed
let user_home = self.get_home_directory()?;
env::set_var("HOME", user_home);
debug!("Home directory for UID {} is {}", uid, user_home);
}

Nik | Klampfradler
committed
// Determine XDG directories now
let base_dirs = match BaseDirectories::with_prefix(BASE_NAME) {
Ok(b) => b,
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e))
};

Nik | Klampfradler
committed
// Restore $HOME to original if we changed it earlier
if uid != get_original_euid() {

Nik | Klampfradler
committed
if saved_home != None {
env::set_var("HOME", saved_home.unwrap());
} else {
env::remove_var("HOME");
}

Nik | Klampfradler
committed
return Ok(base_dirs);
}

Nik | Klampfradler
committed
fn place_user_cache_file(&mut self, filename: String) -> Result<PathBuf, io::Error> {
match self.get_user_xdg_base_directories() {
Ok(b) => b.place_cache_file(filename),

Nik | Klampfradler
committed
Err(e) => Err(e)
pub fn get_access_token(&mut self) -> &Option<BasicTokenResponse> {

Nik | Klampfradler
committed
// Try to load our acess token if none is known
if self.access_token.is_none() {
debug!("No token in memory, trying to load from file");
self.drop_privileges().ok();
self.access_token = match self.place_user_cache_file(USER_TOKEN_FILENAME.to_string()) {
Ok(path) => match load_json(path) {
Ok(read_token) => Some(read_token),
Err(_) => None
Err(_) => None
};
self.restore_privileges();

Nik | Klampfradler
committed
return &self.access_token;

Nik | Klampfradler
committed
pub fn set_access_token(&mut self, token: BasicTokenResponse) -> Result<(), io::Error> {
self.access_token = Some(token.clone());
debug!("Saved token for in memory");
// Try to write user's token cache file

Nik | Klampfradler
committed
let res = match self.drop_privileges() {
Ok(_) => match self.place_user_cache_file(USER_TOKEN_FILENAME.to_string()) {

Nik | Klampfradler
committed
debug!("Storing token for in cache file");
save_json(path, token)
},
Err(e) => Err(e)
},

Nik | Klampfradler
committed
Err(e) => Err(e)
};
self.restore_privileges();
fn load_json<O: DeserializeOwned>(path: PathBuf) -> Result<O, io::Error> {
let file = fs::File::open(path)?;
let reader = io::BufReader::new(file);
match serde_json::from_reader(reader) {
Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e))
}
}
fn save_json<O: Serialize>(path: PathBuf, obj: O) -> Result<(), io::Error> {
let json = match serde_json::to_string(&obj) {
Ok(j) => j,
Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e))
};
fs::write(path, json)
}

Nik | Klampfradler
committed
static mut is_getpwnam_safe: bool = true;
fn get_is_getpwnam_safe() -> bool {
unsafe {
is_getpwnam_safe
}
}
pub fn set_is_getpwnam_safe(v: bool) {
unsafe {
is_getpwnam_safe = v
}
static mut original_euid: uid_t = uid_t::MAX;
static mut original_euid_set: bool = false;
fn get_original_euid() -> uid_t {
unsafe {
if !original_euid_set {
original_euid = geteuid();
original_euid_set = true;
}
original_euid
}
}

Nik | Klampfradler
committed
lazy_static! {
static ref CACHE: Mutex<Cache<'static>> = Mutex::new(Cache::new());

Nik | Klampfradler
committed
}
pub fn get_cache() -> MutexGuard<'static, Cache<'static>> {

Nik | Klampfradler
committed
CACHE.lock().unwrap()