Newer
Older
/* 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 crate::config::{
get_config,
get_optional,
get_or_error
use crate::oauth::get_data;
use std::collections::HashMap;
use serde_json::value::Value;
use std::fmt;
use std::convert::TryInto;
use libc::{getpwuid, geteuid};
use std::ffi::CStr;
use libnss::interop::Response;
use libnss::passwd::{PasswdHooks, Passwd};
fn nss_hook_prepare() -> Config {
let conf = get_config(None);
let mut log_level = log::LevelFilter::Error;
if get_optional(&conf, "nss.debug").unwrap_or_default() {
log_level = log::LevelFilter::Debug;
}
setup_log(log_level);
return conf;
}
fn get_current_user() -> String {
let euid;
let euser;
unsafe {
euid = geteuid();
euser = CStr::from_ptr((*getpwuid(euid)).pw_name);
};
euser.to_str().ok().unwrap().to_string()
}
// FIXME Provide more specific error types and messages
#[derive(Debug, Clone)]
struct TransformMappingError {
msg: String,
field: String
}
impl fmt::Display for TransformMappingError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Invalid mapping configuration: field={} - {}", self.field, self.msg)
}
}
fn transform_ent(conf: &Config, row: &mut HashMap<String, Value>, map_name: &str) -> Result<(), TransformMappingError> {
let mapping: HashMap<String, HashMap<String, Value>> = get_optional(&conf, &("nss.maps.".to_string() + map_name)).unwrap_or_default();
for (field, rule) in &mapping {
let type_: String = rule.get("type")
.ok_or(TransformMappingError { field: field.to_string(), msg: "No type".to_string() })?
.as_str().unwrap().to_string();
let value: &Value = rule.get("value")
.ok_or(TransformMappingError { field: field.to_string(), msg: "No value".to_string() })?;
if type_ == "static" {
row.insert(field.to_string(), value.clone());
} else if type_ == "rename" {
let old_value: Value = row.remove(&value.as_str().unwrap().to_string())
.ok_or(TransformMappingError { field: field.to_string(), msg: "No value to rename".to_string() })?;
row.insert(field.to_string(), old_value);
} else {
return Err(TransformMappingError { field: field.to_string(), msg: (&("Unknown type ".to_string() + &type_)).to_string() });
};
}
Ok(())
}
fn map_to_passwd(conf: &Config, row: &mut HashMap<String, Value>) -> Result<Passwd, TransformMappingError> {
transform_ent(&conf, row, "passwd")?;
Ok(Passwd {
99
100
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
name: row.get("name")
.ok_or(TransformMappingError { field: "name".to_string(), msg: "No value in JSON data".to_string() })?
.as_str().unwrap().to_string(),
passwd: row.get("passwd")
.ok_or(TransformMappingError { field: "passwd".to_string(), msg: "No value in JSON data".to_string() })?
.as_str().unwrap().to_string(),
uid: row.get("uid")
.ok_or(TransformMappingError { field: "uid".to_string(), msg: "No value in JSON data".to_string() })?
.as_u64()
.ok_or(TransformMappingError { field: "uid".to_string(), msg: "Invalid integer".to_string() })?
.try_into()
.or(Err(TransformMappingError { field: "uid".to_string(), msg: "Overflow converting to u32".to_string() }))?,
gid: row.get("gid")
.ok_or(TransformMappingError { field: "gid".to_string(), msg: "No value in JSON data".to_string() })?
.as_u64()
.ok_or(TransformMappingError { field: "gid".to_string(), msg: "Invalid integer".to_string() })?
.try_into()
.or(Err(TransformMappingError { field: "gid".to_string(), msg: "Overflow converting to u32".to_string() }))?,
gecos: row.get("gecos")
.ok_or(TransformMappingError { field: "gecos".to_string(), msg: "No value in JSON data".to_string() })?
.as_str().unwrap().to_string(),
dir: row.get("dir")
.ok_or(TransformMappingError { field: "dir".to_string(), msg: "No value in JSON data".to_string() })?
.as_str().unwrap().to_string(),
shell: row.get("shell")
.ok_or(TransformMappingError { field: "shell".to_string(), msg: "No value in JSON data".to_string() })?
.as_str().unwrap().to_string(),
})
}
struct OidcPasswd;
impl PasswdHooks for OidcPasswd {
fn get_all_entries() -> Response<Vec<Passwd>> {
let conf = nss_hook_prepare();
let mut cache = get_cache();
let token = match cache.load_user_token(&user) {
None => {
// FIXME Implement fallback to system token
error!("Could not find a user token for {} to request NSS data", user);
return Response::Unavail;
}
let mut data: Vec<HashMap<String, Value>> = match get_data(&conf, "nss", "passwd", token, "") {
Ok(d) => d,
Err(_) => return Response::Unavail
};
let mut passwd_vec: Vec<Passwd> = Vec::new();
for row in &mut data {
match map_to_passwd(&conf, row) {
Ok(p) => passwd_vec.push(p),
Err(e) => error!("Error converting JSON to passwd entry: {}", e)
};
Response::Success(passwd_vec)
fn get_entry_by_uid(uid: libc::uid_t) -> Response<Passwd> {
return Response::Success(Passwd {
name: "test".to_string(),
passwd: "x".to_string(),
uid: 1005,
gid: 1005,
gecos: "Test Account".to_string(),
dir: "/home/test".to_string(),
shell: "/bin/bash".to_string(),
});
}
Response::NotFound
fn get_entry_by_name(name: String) -> Response<Passwd> {
return Response::Success(Passwd {
name: "test".to_string(),
passwd: "x".to_string(),
uid: 1005,
gid: 1005,
gecos: "Test Account".to_string(),
dir: "/home/test".to_string(),
shell: "/bin/bash".to_string(),
});
}
Response::NotFound
}
}
libnss_passwd_hooks!(oidc, OidcPasswd);