nss_kanidm/
core.rs

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