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}