/* 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 std::collections::HashMap; use libc::{geteuid, seteuid, uid_t}; use oauth2::basic::BasicTokenResponse; use std::env; use std::fs; use std::io; use std::path::PathBuf; use xdg::BaseDirectories; use serde::Serialize; use serde::de::DeserializeOwned; use serde_json; const USER_TOKEN_FILENAME: &str = "user_token.json"; pub struct Cache { user_tokens: HashMap<String, BasicTokenResponse>, original_euid: uid_t, prefix: String } impl Cache { pub fn new(prefix: &str) -> Cache { let euid; unsafe { euid = geteuid(); }; Cache { user_tokens: HashMap::new(), original_euid: euid, prefix: prefix.to_string() } } 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); } } fn restore_privileges(&self) { let current_euid; unsafe { current_euid = geteuid(); }; if current_euid != self.original_euid { debug!("Restoring privileges"); let res; unsafe { 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(&mut 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 }, Err(_) => None }; self.restore_privileges(); match new_token { Some(t) => { self.user_tokens.insert(owner.to_string(), t); self.user_tokens.get(owner) }, None => None } } else { debug!("Found token for {} in memory", owner); self.user_tokens.get(owner) } } pub fn save_user_token(&mut self, owner: &String, token: BasicTokenResponse) -> Result<(), io::Error> { self.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(); return res; } pub fn delete_user_token(&mut self, owner: &String) { self.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(); () }, 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) { Ok(o) => Ok(o), 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) }