/* Copyright 2021 Dominik George <dominik.george@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 lazy_static::lazy_static; use std::collections::HashMap; use std::convert::From; use std::time::SystemTime; use libc::{geteuid, seteuid, getpwnam, uid_t}; use std::ffi::CString; use oauth2::basic::BasicTokenResponse; use std::env; use xdg::{BaseDirectories,BaseDirectoriesError}; const TOKEN_DEFAULT_EXPIRES: u64 = 24 * 60 * 60; struct UserToken { access_token: String, expires_at: u64, refresh_token: Option<String>, } impl UserToken { fn is_expired(&self) -> bool { match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { Ok(d) => d.as_secs() >= self.expires_at, Err(_) => true } } } impl From<BasicTokenResponse> for UserToken { fn from(response: BasicTokenResponse) -> Self { UserToken { access_token: response.access_token.secret().to_string(), expires_at: match response.expires_in { Some(duration) => duration, None => TOKEN_DEFAULT_EXPIRES } + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).ok().unwrap().as_secs(), refresh_token: match response.refresh_token { Some(t) => Some(t.secret().to_string()), None => None } } } } struct Cache { user_tokens: HashMap<String, UserToken>, original_euid: uid_t } impl Cache { pub fn new() -> Cache { Cache { user_tokens: HashMap::new(), original_euid: geteuid() } } fn drop_privileges(&self, username: String) -> Result<uid_t, String> { let nam = match CString::new(username) { Ok(nam) => nam, Err(_) => return Err("Invalid username in lookup".to_string()) }; let target_euid = (*getpwnam(nam.as_ptr())).pw_uid; if target_euid == self.original_euid { return Ok(self.original_euid); } else if self.original_euid == 0 { seteuid(target_euid); return Ok(target_euid); } return Err("Dropping privileges not supported".to_string()); } fn restore_privileges(&self) { seteuid(self.original_euid); } fn get_user_xdg_base_directories(&self, username: String) -> Result<BaseDirectories, BaseDirectoriesError> { let saved_home = env::var_os("HOME"); let nam = match CString::new(username) { Ok(nam) => nam, Err(_) => CString::new("nobody").ok().unwrap() }; let user_home = CString::from_raw((*getpwnam(nam.as_ptr())).pw_dir).to_str().unwrap(); env::set_var("HOME", user_home); let base_dirs = BaseDirectories::new()?; if saved_home != None { env::set_var("HOME", saved_home.unwrap()); } else { env::remove_var("HOME"); } return Ok(base_dirs); } pub fn load_user_token(&self, owner: String) -> Option<&UserToken> { return self.user_tokens.get(&owner); } pub fn save_user_token(&self, owner: String, token: UserToken) { self.user_tokens.insert(owner, token); } pub fn delete_user_token(&self, owner: String) { self.user_tokens.remove(&owner); // Try to remove user's token cache file self.drop_privileges(owner).ok(); // FIXME Add delete code here self.restore_privileges(); } pub fn cleanup_tokens(&self) { for (owner, token) in self.user_tokens { if token.is_expired() { self.delete_user_token(owner); } } } } lazy_static! { pub static ref CACHE: Cache = Cache::new(); }