nss_kanidm/
core.rs
1use kanidm_unix_common::client_sync::DaemonClientBlocking;
2use kanidm_unix_common::unix_config::PamNssConfig;
3use kanidm_unix_common::unix_passwd::{
4 read_etc_group_file, read_etc_passwd_file, EtcGroup, EtcUser,
5};
6use kanidm_unix_common::unix_proto::{ClientRequest, ClientResponse, NssGroup, NssUser};
7
8use libnss::group::Group;
9use libnss::interop::Response;
10use libnss::passwd::Passwd;
11
12#[cfg(test)]
13use kanidm_unix_common::client_sync::UnixStream;
14
15pub enum RequestOptions {
16 Main {
17 config_path: &'static str,
18 },
19 #[cfg(test)]
20 Test {
21 socket: Option<UnixStream>,
22 users: Vec<EtcUser>,
23 groups: Vec<EtcGroup>,
24 },
25}
26
27enum Source {
28 Daemon(DaemonClientBlocking),
29 Fallback {
30 users: Vec<EtcUser>,
31 groups: Vec<EtcGroup>,
32 },
33}
34
35impl RequestOptions {
36 fn connect_to_daemon(self) -> Source {
37 match self {
38 RequestOptions::Main { config_path } => {
39 let maybe_client = PamNssConfig::new()
40 .read_options_from_optional_config(config_path)
41 .ok()
42 .and_then(|cfg| {
43 DaemonClientBlocking::new(cfg.sock_path.as_str(), cfg.unix_sock_timeout)
44 .ok()
45 });
46
47 if let Some(client) = maybe_client {
48 Source::Daemon(client)
49 } else {
50 let users = read_etc_passwd_file("/etc/passwd").unwrap_or_default();
51
52 let groups = read_etc_group_file("/etc/group").unwrap_or_default();
53
54 Source::Fallback { users, groups }
55 }
56 }
57 #[cfg(test)]
58 RequestOptions::Test {
59 socket,
60 users,
61 groups,
62 } => {
63 if let Some(socket) = socket {
64 Source::Daemon(DaemonClientBlocking::from(socket))
65 } else {
66 Source::Fallback { users, groups }
67 }
68 }
69 }
70 }
71}
72
73pub fn get_all_user_entries(req_options: RequestOptions) -> Response<Vec<Passwd>> {
74 match req_options.connect_to_daemon() {
75 Source::Daemon(mut daemon_client) => {
76 let req = ClientRequest::NssAccounts;
77
78 daemon_client
79 .call_and_wait(&req, None)
80 .map(|r| match r {
81 ClientResponse::NssAccounts(l) => {
82 l.into_iter().map(passwd_from_nssuser).collect()
83 }
84 _ => Vec::new(),
85 })
86 .map(Response::Success)
87 .unwrap_or_else(|_| Response::Success(vec![]))
88 }
89 Source::Fallback { users, groups: _ } => {
90 if users.is_empty() {
91 return Response::Unavail;
92 }
93
94 let users = users.into_iter().map(passwd_from_etcuser).collect();
95
96 Response::Success(users)
97 }
98 }
99}
100
101pub fn get_user_entry_by_uid(uid: libc::uid_t, req_options: RequestOptions) -> Response<Passwd> {
102 match req_options.connect_to_daemon() {
103 Source::Daemon(mut daemon_client) => {
104 let req = ClientRequest::NssAccountByUid(uid);
105 daemon_client
106 .call_and_wait(&req, None)
107 .map(|r| match r {
108 ClientResponse::NssAccount(opt) => opt
109 .map(passwd_from_nssuser)
110 .map(Response::Success)
111 .unwrap_or_else(|| Response::NotFound),
112 _ => Response::NotFound,
113 })
114 .unwrap_or_else(|_| Response::NotFound)
115 }
116 Source::Fallback { users, groups: _ } => {
117 if users.is_empty() {
118 return Response::Unavail;
119 }
120
121 let user = users
122 .into_iter()
123 .filter_map(|etcuser| {
124 if etcuser.uid == uid {
125 Some(passwd_from_etcuser(etcuser))
126 } else {
127 None
128 }
129 })
130 .next();
131
132 if let Some(user) = user {
133 Response::Success(user)
134 } else {
135 Response::NotFound
136 }
137 }
138 }
139}
140
141pub fn get_user_entry_by_name(name: String, req_options: RequestOptions) -> Response<Passwd> {
142 match req_options.connect_to_daemon() {
143 Source::Daemon(mut daemon_client) => {
144 let req = ClientRequest::NssAccountByName(name);
145 daemon_client
146 .call_and_wait(&req, None)
147 .map(|r| match r {
148 ClientResponse::NssAccount(opt) => opt
149 .map(passwd_from_nssuser)
150 .map(Response::Success)
151 .unwrap_or_else(|| Response::NotFound),
152 _ => Response::NotFound,
153 })
154 .unwrap_or_else(|_| Response::NotFound)
155 }
156 Source::Fallback { users, groups: _ } => {
157 if users.is_empty() {
158 return Response::Unavail;
159 }
160
161 let user = users
162 .into_iter()
163 .filter_map(|etcuser| {
164 if etcuser.name == name {
165 Some(passwd_from_etcuser(etcuser))
166 } else {
167 None
168 }
169 })
170 .next();
171
172 if let Some(user) = user {
173 Response::Success(user)
174 } else {
175 Response::NotFound
176 }
177 }
178 }
179}
180
181pub fn get_all_group_entries(req_options: RequestOptions) -> Response<Vec<Group>> {
182 match req_options.connect_to_daemon() {
183 Source::Daemon(mut daemon_client) => {
184 let req = ClientRequest::NssGroups;
185 daemon_client
186 .call_and_wait(&req, None)
187 .map(|r| match r {
188 ClientResponse::NssGroups(l) => {
189 l.into_iter().map(group_from_nssgroup).collect()
190 }
191 _ => Vec::new(),
192 })
193 .map(Response::Success)
194 .unwrap_or_else(|_| Response::Success(vec![]))
195 }
196 Source::Fallback { users: _, groups } => {
197 if groups.is_empty() {
198 return Response::Unavail;
199 }
200
201 let groups = groups.into_iter().map(group_from_etcgroup).collect();
202
203 Response::Success(groups)
204 }
205 }
206}
207
208pub fn get_group_entry_by_gid(gid: libc::gid_t, req_options: RequestOptions) -> Response<Group> {
209 match req_options.connect_to_daemon() {
210 Source::Daemon(mut daemon_client) => {
211 let req = ClientRequest::NssGroupByGid(gid);
212 daemon_client
213 .call_and_wait(&req, None)
214 .map(|r| match r {
215 ClientResponse::NssGroup(opt) => opt
216 .map(group_from_nssgroup)
217 .map(Response::Success)
218 .unwrap_or_else(|| Response::NotFound),
219 _ => Response::NotFound,
220 })
221 .unwrap_or_else(|_| Response::NotFound)
222 }
223 Source::Fallback { users: _, groups } => {
224 if groups.is_empty() {
225 return Response::Unavail;
226 }
227
228 let group = groups
229 .into_iter()
230 .filter_map(|etcgroup| {
231 if etcgroup.gid == gid {
232 Some(group_from_etcgroup(etcgroup))
233 } else {
234 None
235 }
236 })
237 .next();
238
239 if let Some(group) = group {
240 Response::Success(group)
241 } else {
242 Response::NotFound
243 }
244 }
245 }
246}
247
248pub fn get_group_entry_by_name(name: String, req_options: RequestOptions) -> Response<Group> {
249 match req_options.connect_to_daemon() {
250 Source::Daemon(mut daemon_client) => {
251 let req = ClientRequest::NssGroupByName(name);
252 daemon_client
253 .call_and_wait(&req, None)
254 .map(|r| match r {
255 ClientResponse::NssGroup(opt) => opt
256 .map(group_from_nssgroup)
257 .map(Response::Success)
258 .unwrap_or_else(|| Response::NotFound),
259 _ => Response::NotFound,
260 })
261 .unwrap_or_else(|_| Response::NotFound)
262 }
263 Source::Fallback { users: _, groups } => {
264 if groups.is_empty() {
265 return Response::Unavail;
266 }
267
268 let group = groups
269 .into_iter()
270 .filter_map(|etcgroup| {
271 if etcgroup.name == name {
272 Some(group_from_etcgroup(etcgroup))
273 } else {
274 None
275 }
276 })
277 .next();
278
279 if let Some(group) = group {
280 Response::Success(group)
281 } else {
282 Response::NotFound
283 }
284 }
285 }
286}
287
288fn passwd_from_etcuser(etc: EtcUser) -> Passwd {
289 Passwd {
290 name: etc.name,
291 gecos: etc.gecos,
292 passwd: "x".to_string(),
293 uid: etc.uid,
294 gid: etc.gid,
295 dir: etc.homedir,
296 shell: etc.shell,
297 }
298}
299
300fn passwd_from_nssuser(nu: NssUser) -> Passwd {
301 Passwd {
302 name: nu.name,
303 gecos: nu.gecos,
304 passwd: "x".to_string(),
305 uid: nu.uid,
306 gid: nu.gid,
307 dir: nu.homedir,
308 shell: nu.shell,
309 }
310}
311
312fn group_from_etcgroup(etc: EtcGroup) -> Group {
313 Group {
314 name: etc.name,
315 passwd: "x".to_string(),
316 gid: etc.gid,
317 members: etc.members,
318 }
319}
320
321fn group_from_nssgroup(ng: NssGroup) -> Group {
322 Group {
323 name: ng.name,
324 passwd: "x".to_string(),
325 gid: ng.gid,
326 members: ng.members,
327 }
328}