use kanidm_unix_common::client_sync::DaemonClientBlocking; use kanidm_unix_common::unix_config::PamNssConfig; use kanidm_unix_common::unix_passwd::{ read_etc_group_file, read_etc_passwd_file, EtcGroup, EtcUser, }; use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse, NssGroup, NssUser}; use libnss::group::Group; use libnss::interop::Response; use libnss::passwd::Passwd; #[cfg(test)] use kanidm_unix_common::client_sync::UnixStream; pub enum RequestOptions { Main { config_path: &'static str, }, #[cfg(test)] Test { socket: Option<UnixStream>, users: Vec<EtcUser>, groups: Vec<EtcGroup>, }, } enum Source { Daemon(DaemonClientBlocking), Fallback { users: Vec<EtcUser>, groups: Vec<EtcGroup>, }, } impl RequestOptions { fn connect_to_daemon(self) -> Source { match self { RequestOptions::Main { config_path } => { let maybe_client = PamNssConfig::new() .read_options_from_optional_config(config_path) .ok() .and_then(|cfg| { DaemonClientBlocking::new(cfg.sock_path.as_str(), cfg.unix_sock_timeout) .ok() }); if let Some(client) = maybe_client { Source::Daemon(client) } else { let users = read_etc_passwd_file("/etc/passwd").unwrap_or_default(); let groups = read_etc_group_file("/etc/group").unwrap_or_default(); Source::Fallback { users, groups } } } #[cfg(test)] RequestOptions::Test { socket, users, groups, } => { if let Some(socket) = socket { Source::Daemon(DaemonClientBlocking::from(socket)) } else { Source::Fallback { users, groups } } } } } } pub fn get_all_user_entries(req_options: RequestOptions) -> Response<Vec<Passwd>> { match req_options.connect_to_daemon() { Source::Daemon(mut daemon_client) => { let req = ClientRequest::NssAccounts; daemon_client .call_and_wait(&req, None) .map(|r| match r { ClientResponse::NssAccounts(l) => { l.into_iter().map(passwd_from_nssuser).collect() } _ => Vec::new(), }) .map(Response::Success) .unwrap_or_else(|_| Response::Success(vec![])) } Source::Fallback { users, groups: _ } => { if users.is_empty() { return Response::Unavail; } let users = users.into_iter().map(passwd_from_etcuser).collect(); Response::Success(users) } } } pub fn get_user_entry_by_uid(uid: libc::uid_t, req_options: RequestOptions) -> Response<Passwd> { match req_options.connect_to_daemon() { Source::Daemon(mut daemon_client) => { let req = ClientRequest::NssAccountByUid(uid); daemon_client .call_and_wait(&req, None) .map(|r| match r { ClientResponse::NssAccount(opt) => opt .map(passwd_from_nssuser) .map(Response::Success) .unwrap_or_else(|| Response::NotFound), _ => Response::NotFound, }) .unwrap_or_else(|_| Response::NotFound) } Source::Fallback { users, groups: _ } => { if users.is_empty() { return Response::Unavail; } let user = users .into_iter() .filter_map(|etcuser| { if etcuser.uid == uid { Some(passwd_from_etcuser(etcuser)) } else { None } }) .next(); if let Some(user) = user { Response::Success(user) } else { Response::NotFound } } } } pub fn get_user_entry_by_name(name: String, req_options: RequestOptions) -> Response<Passwd> { match req_options.connect_to_daemon() { Source::Daemon(mut daemon_client) => { let req = ClientRequest::NssAccountByName(name); daemon_client .call_and_wait(&req, None) .map(|r| match r { ClientResponse::NssAccount(opt) => opt .map(passwd_from_nssuser) .map(Response::Success) .unwrap_or_else(|| Response::NotFound), _ => Response::NotFound, }) .unwrap_or_else(|_| Response::NotFound) } Source::Fallback { users, groups: _ } => { if users.is_empty() { return Response::Unavail; } let user = users .into_iter() .filter_map(|etcuser| { if etcuser.name == name { Some(passwd_from_etcuser(etcuser)) } else { None } }) .next(); if let Some(user) = user { Response::Success(user) } else { Response::NotFound } } } } pub fn get_all_group_entries(req_options: RequestOptions) -> Response<Vec<Group>> { match req_options.connect_to_daemon() { Source::Daemon(mut daemon_client) => { let req = ClientRequest::NssGroups; daemon_client .call_and_wait(&req, None) .map(|r| match r { ClientResponse::NssGroups(l) => { l.into_iter().map(group_from_nssgroup).collect() } _ => Vec::new(), }) .map(Response::Success) .unwrap_or_else(|_| Response::Success(vec![])) } Source::Fallback { users: _, groups } => { if groups.is_empty() { return Response::Unavail; } let groups = groups.into_iter().map(group_from_etcgroup).collect(); Response::Success(groups) } } } pub fn get_group_entry_by_gid(gid: libc::gid_t, req_options: RequestOptions) -> Response<Group> { match req_options.connect_to_daemon() { Source::Daemon(mut daemon_client) => { let req = ClientRequest::NssGroupByGid(gid); daemon_client .call_and_wait(&req, None) .map(|r| match r { ClientResponse::NssGroup(opt) => opt .map(group_from_nssgroup) .map(Response::Success) .unwrap_or_else(|| Response::NotFound), _ => Response::NotFound, }) .unwrap_or_else(|_| Response::NotFound) } Source::Fallback { users: _, groups } => { if groups.is_empty() { return Response::Unavail; } let group = groups .into_iter() .filter_map(|etcgroup| { if etcgroup.gid == gid { Some(group_from_etcgroup(etcgroup)) } else { None } }) .next(); if let Some(group) = group { Response::Success(group) } else { Response::NotFound } } } } pub fn get_group_entry_by_name(name: String, req_options: RequestOptions) -> Response<Group> { match req_options.connect_to_daemon() { Source::Daemon(mut daemon_client) => { let req = ClientRequest::NssGroupByName(name); daemon_client .call_and_wait(&req, None) .map(|r| match r { ClientResponse::NssGroup(opt) => opt .map(group_from_nssgroup) .map(Response::Success) .unwrap_or_else(|| Response::NotFound), _ => Response::NotFound, }) .unwrap_or_else(|_| Response::NotFound) } Source::Fallback { users: _, groups } => { if groups.is_empty() { return Response::Unavail; } let group = groups .into_iter() .filter_map(|etcgroup| { if etcgroup.name == name { Some(group_from_etcgroup(etcgroup)) } else { None } }) .next(); if let Some(group) = group { Response::Success(group) } else { Response::NotFound } } } } fn passwd_from_etcuser(etc: EtcUser) -> Passwd { Passwd { name: etc.name, gecos: etc.gecos, passwd: "x".to_string(), uid: etc.uid, gid: etc.gid, dir: etc.homedir, shell: etc.shell, } } fn passwd_from_nssuser(nu: NssUser) -> Passwd { Passwd { name: nu.name, gecos: nu.gecos, passwd: "x".to_string(), uid: nu.uid, gid: nu.gid, dir: nu.homedir, shell: nu.shell, } } fn group_from_etcgroup(etc: EtcGroup) -> Group { Group { name: etc.name, passwd: "x".to_string(), gid: etc.gid, members: etc.members, } } fn group_from_nssgroup(ng: NssGroup) -> Group { Group { name: ng.name, passwd: "x".to_string(), gid: ng.gid, members: ng.members, } }