Skip to content
Snippets Groups Projects
cache.rs 7.38 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::getpwnam_safe;

use lazy_static::lazy_static;
use std::collections::HashMap;
use std::sync::{RwLock, RwLockReadGuard};
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";
pub struct Cache {
    user_tokens: HashMap<String, BasicTokenResponse>,
    original_euid: uid_t,
    pub fn new() -> Cache {
        let euid;
        unsafe {
            euid = geteuid();
        };
            user_tokens: HashMap::new(),
            original_euid: euid
    fn drop_privileges(&self, username: &String) -> Result<uid_t, io::Error> {
        let current_euid;
        unsafe {
            current_euid = geteuid();
        };

        let pw = match getpwnam_safe(username.to_string()) {
            Ok(p) => p,
            Err(e) => {
                error!("Failed to lookup user {}: {}", username, e);
                return Err(e);
            }
        debug!("Lookup for user {} returned UID {}, GID {}", username, pw.pw_uid, pw.pw_gid);
        let target_euid = pw.pw_uid;
        if target_euid == current_euid {
            debug!("No need to drop privileges, already running as {}", username);
            return Ok(current_euid);
        let res;
        unsafe {
            res = seteuid(target_euid);
        };
        if res == 0 {
            debug!("Successfully dropped privileges to {}", username);
            return Ok(target_euid);
        } else {
            let e = io::Error::last_os_error();
            error!("Could not drop privileges to {}", username);
            return Err(e);
        }
            current_euid = geteuid();
        };

        if current_euid != self.original_euid {
            debug!("Restoring privileges");
                res = seteuid(self.original_euid);
            if res != 0 {
                panic!("Could not restore privileges to {}", self.original_euid);
            }
        } else {
            debug!("No need to restore privileges, already running as original user");
    fn get_user_xdg_base_directories(&self, username: &String) -> Result<BaseDirectories, io::Error> {
        let saved_home = env::var_os("HOME");

        let pw = match getpwnam_safe(username.to_string()) {
            Ok(p) => p,
            Err(e) => {
                error!("Failed to lookup user {}: {}", username, e);
                return Err(e);
            }
        debug!("Lookup for user {} returned UID {}, GID {}", username, pw.pw_uid, pw.pw_gid);
        let user_home = pw.pw_dir;
        env::set_var("HOME", &user_home);
        debug!("Home directory for {} is {}", username, 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))
        };
        if saved_home != None {
            env::set_var("HOME", saved_home.unwrap());
        } else {
            env::remove_var("HOME");
        }
        return Ok(base_dirs);
    }

    fn place_user_cache_file(&self, username: &String, filename: String) -> Result<PathBuf, io::Error> {
        match self.get_user_xdg_base_directories(username) {
            Ok(b) => b.place_cache_file(filename),
            Err(e) => Err(io::Error::new(io::ErrorKind::NotFound, e))
        }
    }

    pub fn load_user_token(&self, owner: &String) -> Option<&BasicTokenResponse> {
        if !self.user_tokens.contains_key(owner) {
            debug!("No token for {} in memory, trying to load from file", owner);

            self.drop_privileges(owner).ok();
            let new_token = match self.place_user_cache_file(owner, USER_TOKEN_FILENAME.to_string()) {
                Ok(path) => match load_json(path) {
                    Ok(read_token) => Some(read_token),
                    Err(_) => None
            };
            self.restore_privileges();
                    CACHE.write().unwrap().user_tokens.insert(owner.to_string(), t);
                    self.user_tokens.get(owner)
                },
                None => None
            }
            debug!("Found token for {} in memory", owner);
            self.user_tokens.get(owner)
    pub fn save_user_token(&self, owner: &String, token: BasicTokenResponse) -> Result<(), io::Error> {
        CACHE.write().unwrap().user_tokens.insert(owner.to_string(), token.clone());
        debug!("Saved token for {} in memory", owner);

        // Try to write user's token cache file
        let res = match self.drop_privileges(owner) {
            Ok(_) => match self.place_user_cache_file(owner, USER_TOKEN_FILENAME.to_string()) {
                Ok(path) => {
                    debug!("Storing token for {} in cache file", owner);
                    save_json(path, token)
                },
                Err(e) => Err(e)
            },
            Err(e) => Err(io::Error::new(io::ErrorKind::PermissionDenied, e))
        };
        self.restore_privileges();
    pub fn delete_user_token(&self, owner: &String) {
        CACHE.write().unwrap().user_tokens.remove(owner);
        debug!("Token for {} removed from memory", owner);

        // Try to remove user's token cache file
        self.drop_privileges(owner).ok();
        match self.place_user_cache_file(owner, USER_TOKEN_FILENAME.to_string()) {
            Ok(path) => {
                debug!("Deleting cache file for {}", owner);
                fs::remove_file(path).ok();
                ()
        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)
}

lazy_static! {
    static ref CACHE: RwLock<Cache> = RwLock::new(Cache::new());
}

pub fn get_cache() -> RwLockReadGuard<'static, Cache> {
    CACHE.read().unwrap()
}