Skip to content
Snippets Groups Projects
cache.rs 9.18 KiB
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.
 */

use crate::BASE_NAME;
use crate::unix::{Passwd, getpwnam_safe, getpwuid_safe};
use lazy_static::lazy_static;
use libc::{geteuid, seteuid, uid_t};
use oauth2::basic::BasicTokenResponse;

use std::io;
use std::path::PathBuf;
use xdg::BaseDirectories;
use serde::Serialize;
use serde::de::DeserializeOwned;
const USER_TOKEN_FILENAME: &str = "user_token.json";
    username: Option<&'a str>,
    passwd: Option<Passwd<'a>>,
pub struct Cache<'a> {
    pub context_user: UserInfo<'a>
impl Cache<'_> {
    pub fn new<'a>() -> Cache<'a> {
            context_user: UserInfo {
                uid: None,
                username: None,
                passwd: None,
                access_token: None
            }
    pub fn set_current_user(&mut self) {
        self.set_uid(original_euid);
    }

    pub fn is_initialized(&self) -> bool {
        self.uid.is_some() || self.username.is_some()
    }
    fn try_resolve(&mut self) -> Result<&Passwd, io::Error> {
        // If we already have a full passwd struct, return it as without resolving
        if self.passwd.is_some() {
            debug!("passwd entry for context user already resolved");
            return Ok(self.passwd.as_ref().unwrap());
        }

        // If we cannot call getpwnam safely, return error (see `is_get_pwnam_safe`)
        if !is_getpwnam_safe() {
            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))
                }
            }
        match res {
            Ok(passwd) => {
                debug!("Successfully resolved context user's passwd entry");
                self.passwd = Some(passwd);
                Ok(self.passwd.as_ref().unwrap())
            },
            Err(e) => Err(e)
        }
    }

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

    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<&str, io::Error> {
            Ok(passwd) => Ok(passwd.pw_name),
            Err(e) => match self.username {
                Some(username) => Ok(username),
                None => Err(e)
            }
        }
    }

    pub fn set_username(&mut self, username: &'a str) {
        self.username = Some(username);
        self.uid = None;
        self.passwd = None;
        self.try_resolve();
    }

    pub fn get_home_directory(&mut self) -> Result<&str, io::Error> {
        match self.try_resolve() {
            Ok(passwd) => Ok(passwd.pw_dir),
            Err(e) => Err(e)
        }
    }

    fn drop_privileges(&self) -> Result<uid_t, io::Error> {
        let current_euid = unsafe {
            geteuid()
        };

        let target_euid = match self.get_uid() {
            Ok(uid) => uid,
                error!("Could not drop privileges because target UID is not resolved");
        if target_euid == current_euid {
            debug!("No need to drop privileges, already running as {}", current_euid);
            return Ok(current_euid);
            debug!("Successfully dropped privileges to {}", target_euid);
            return Ok(target_euid);
        } else {
            let e = io::Error::last_os_error();
            error!("Could not drop privileges to {}", target_euid);
            debug!("Restoring privileges");
                panic!("Could not restore privileges to {}", original_euid);
        } else {
            debug!("No need to restore privileges, already running as original user");
    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");

        // Determine user ID to find out whether we should override $HOME
        let uid = self.get_uid()?;
        if uid != original_euid {
            let user_home = self.get_home_directory()?;
            env::set_var("HOME", user_home);
            debug!("Home directory for UID {} is {}", uid, user_home);
        }
        let base_dirs = match BaseDirectories::with_prefix(BASE_NAME) {
            Ok(b) => b,
            Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e))
        };

        // Restore $HOME to original if we changed it earlier
        if uid != original_euid {
            if saved_home != None {
                env::set_var("HOME", saved_home.unwrap());
            } else {
                env::remove_var("HOME");
            }
    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),
    pub fn get_access_token(&mut self) -> Option<BasicTokenResponse> {
        // 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
            };
            self.restore_privileges();
    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
        let res = match self.drop_privileges() {
            Ok(_) => match self.place_user_cache_file(USER_TOKEN_FILENAME.to_string()) {
                    save_json(path, token)
                },
                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)
}
fn is_getpwnam_safe() -> bool {
    // FIXME Implement real logic
    return true;
static original_euid: uid_t = unsafe {
    geteuid()
};

lazy_static! {
    static ref CACHE: Mutex<Cache<'static>> = Mutex::new(Cache::new());
pub fn get_cache() -> MutexGuard<'static, Cache<'static>> {