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";

Nik | Klampfradler
committed
struct UserInfo {
uid: Option<uid_t>,
username: Option<String>,
passwd: Option<Passwd>,
access_token: Option<BasicTokenResponse>
}

Nik | Klampfradler
committed
pub context_user: UserInfo
}
impl Cache {

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 UserInfo {
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()
}

Nik | Klampfradler
committed
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))
}
}

Nik | Klampfradler
committed
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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();
}

Nik | Klampfradler
committed
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
pub fn get_username(&mut self) -> Result<String, io::Error> {
match self.try_resolve() {
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: String) {
self.username = Some(username);
self.uid = None;
self.passwd = None;
self.try_resolve();
}
pub fn get_home_directory(&mut self) -> Result<String, 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,

Nik | Klampfradler
committed
error!("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()

Nik | Klampfradler
committed
if current_euid != original_euid {
debug!("Restoring privileges");

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

Nik | Klampfradler
committed
panic!("Could not restore privileges to {}", 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 != original_euid {
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 != original_euid {
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)

Nik | Klampfradler
committed
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
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
fn is_getpwnam_safe() -> bool {
// FIXME Implement real logic
return true;

Nik | Klampfradler
committed
static original_euid: uid_t = unsafe {
geteuid()
};
lazy_static! {
static ref CACHE: Mutex<Cache> = Mutex::new(Cache::new());
}
pub fn get_cache() -> MutexGuard<'static, Cache> {
CACHE.lock().unwrap()